How to implement query a resource by multi dimesions in flask-restful? - python-3.x

As a new just learning flask, I have a few questions.
class Users(Resource):
decorators = [...,]
def get(self, user_id):
"""
GET /users : all uesers
GET /users/1 : user where user_id = 1
"""
# do query user by user_id
In real business, thers is rarely queried users based on only one dimension, instead, most of the time, we need to query users based on multiple dimensions, such as
query users by name
query users by email address
query users by name and create_time
More ...
My Questions:
Q1: as I know, there is only on get method in a flask-restful resource class, how to support so many query user functions in one get methon?
many be like this,
def get(user_id=None, user_email=None, user_create_time=None, user_name=None):
if user_id is None and user_email is None and user_create_time is None and user_name:
query_user_by_name
elif user_id is None and user_email and user_create_time is None and user_name is None:
query_user_by_email
...
OH...HO..., It's smelly and long, really a disaster!
Q2: I also think that use different Resource, like:
class UserNameResource(Resource):
def get(user_name):
query_user_by_name
class UserNameAndCreateTimeResource(Resource):
def get(user_name, create_time):
query_user_by_name_and create_time
...
It's also long, ugly, and doesn't conform to restful naming rules, this is my second question.
Q3: Use post methon instead of get, because restful is not restricted to use POST method to query a resource. If this is Ok, so here comes my third question: how to distinguish 'create a resource' from 'query a resource' in a post method? Maybe with my limited understanding, like this?
class Users(Resource):
def post(_method, **kwargs):
if _method == 'QUERY': # just a flag, could be others, as long as it's agreed in advance
# query_resource, then query user by diffenent conditions
if query_string in kwargs:
# query_sring is a dict, could used to ORM query,
# so there is only one method for query users resouce,
# there is no more other query_users_funcs like
# query_user_by_name, query_user_id,
# query_user_by_name_and_email ...
query_user_by_query_string
if _method == 'CREATE':
create_resource
I'm a newcomer to flask and restful. The above are my confusing, problems, and my own thoughts, which may be very immature.
Welcome to discuss and guide me. Thank you very much.

Related

When to use SQL Foreign key using peewee?

I'm currently using PeeWee together with Python and I have managed to create a decent beginner
CREATE TABLE stores (
id SERIAL PRIMARY KEY,
store_name TEXT
);
CREATE TABLE products (
id SERIAL,
store_id INTEGER NOT NULL,
title TEXT,
image TEXT,
url TEXT UNIQUE,
added_date timestamp without time zone NOT NULL DEFAULT NOW(),
PRIMARY KEY(id, store_id)
);
ALTER TABLE products
ADD CONSTRAINT "FK_products_stores" FOREIGN KEY ("store_id")
REFERENCES stores (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT;
which has been converted to peewee by following code:
# ------------------------------------------------------------------------------- #
class Stores(Model):
id = IntegerField(column_name='id')
store_name = TextField(column_name='store_name')
class Meta:
database = postgres_pool
db_table = "stores"
#classmethod
def get_all(cls):
try:
return cls.select(cls.id, cls.store_name).order_by(cls.store)
except Stores.IntegrityError:
return None
# ------------------------------------------------------------------------------- #
class Products(Model):
id = IntegerField(column_name='id')
store_id = TextField(column_name='store_id')
title = TextField(column_name='title')
url = TextField(column_name='url')
image = TextField(column_name='image')
store = ForeignKeyField(Stores, backref='products')
class Meta:
database = postgres_pool
db_table = "products"
#classmethod
def get_all_products(cls, given_id):
try:
return cls.select().where(cls.store_id == given_id)
except Stores.IntegrityError:
return None
#classmethod
def add_product(cls, pageData, store_id):
"""
INSERT
INTO
public.products(store_id, title, image, url)
VALUES((SELECT id FROM stores WHERE store_name = 'footish'), 'Teva Flatform Universal Pride',
'https://www.footish.se/sneakers/teva-flatform-universal-pride-t51116376',
'https://www.footish.se/pub_images/large/teva-flatform-universal-pride-t1116376-p77148.jpg?timestamp=1623417840')
"""
try:
return cls.insert(
store_id=store_id,
title=pageData.title,
url=pageData.url,
image=pageData.image,
).execute()
except Products.DoesNotExist:
return None
except peewee.IntegrityError as err:
print(f"error: {err}")
return None
My idea is that when I start my application, I would have a constant variable which a store_id set already e.g. 1. With that it would make the execution of queries faster as I do not need another select to get the store_id by a store_name. However looking at my code. I have a field that is: store = ForeignKeyField(Stores, backref='products') where I am starting to think what do I need it in my application.
I am aware that I do have a FK from my ALTER query but in my application that I have written I cannot see a reason why I would need to type in the the foreign key at all but I would like some help to understand more why and how I could use the value "store" in my applciation. It could be as I think that I might not need it at all?
Hello! By reading your initial idea about making "the execution of queries faster" from having a constant variable, the first thing that came to mind was the hassle of always having to manually edit the variable. This is poor practice and not something you'd want to do on a professional application. To obtain the value you should use, I suggest running a query programmatically and fetching the id's highest value using SQL's MAX() function.
As for the foreign key, you don't have to use it, but it can be good practice when it matters. In this case, look at your FK constraint: it has an ON DELETE RESTRICT statement, which cancels any delete operation on the parent table if it has data being used as a foreign key in another table. This would require going to the other table, the one with the foreign key, and deleting every row related to the one on the previous table before being able to delete it.
In general, if you have two tables with information linked in any way, I'd highly suggest using keys. It increases organization and, if proper constraints are added, it increases both readability for external users and reduces errors.
When it comes to using the store you mentioned, you might want to have an API return all products related to a single store. Or all products except from a specific one.
I tried to keep things simple due to not being fully confident I understood the question. I hope this was helpful.

How to add a default filter parameter to every query in mongoengine?

I've been researching a lot, but I haven't found a way.
I have Document clases with a _owner attribute which specifies the ObjectID of the owner, which is a per-request value, so it's globally available. I would like to be able to set part of the query by default.
For example, doing this query
MyClass.objects(id='12345')
should be the same as doing
MyClass.objects(id='12345', _owner=global.owner)
because _owner=global.owner is always added by default
I haven't found a way to override objects, and using a queryset_classis someway confusing because I still have to remember to call a ".owned()" manager to add the filter every time I want to query something.
It ends up like this...
MyClass.objects(id='12345').owned()
// same that ...
MyClass.objects(id='12345', _owner=global.owner)
Any Idea? Thanks!
The following should do the trick for querying (example is simplified by using a constant owned=True but it can easily be extended to use your global):
class OwnedHouseWrapper(object):
# Implements descriptor protocol
def __get__(self, instance, owner):
return House.objects.filter(owned=True)
def __set__(self, instance, value):
raise Exception("can't set .objects")
class House(Document):
address = StringField()
owned = BooleanField(default=False)
class OwnedHouse:
objects = OwnedHouseWrapper()
House(address='garbage 12', owned=True).save()
print(OwnedHouse.objects()) # [<House: House object>]
print(len(OwnedHouse.objects)) # 1

How do I read the parameter of the GET request in a RESTful Flask/SQLAlchemy/Marshmallow environment

I was working my way through a couple of tutorials before xmas and I'm now trying to pick up where I left of.
Trying to teach myself REST by building some simple API end points. My confusion is coming from the fact that I cant find the tutorials I was using and there seems to be several different ways to solve the problem. So now I'm not sure what is the correct way to do it.
The code is working for returning all customers in the DB, now I want to return a specific customer based on their Id
Ok this is what I have...
I have an app.py that defines the resource like this:
api.add_resource(CustomerResource, '/Customer')
I have a models.py that defines the customer class like this:
ma = Marshmallow()
db = SQLAlchemy()
class Customer(db.Model):
__tablename__ = 'customers'
__table_args__ = {"schema":"business"}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
status = db.Column(db.Integer, nullable=False)
class CustomerSchema(ma.Schema):
id = fields.Integer()
name = fields.String(required=True)
status = fields.Integer(required=True)
I have customer.py that defines the customer class as this:
customers_schema = CustomerSchema(many=True)
customer_schema = CustomerSchema()
class CustomerResource(Resource):
def get(self):
customers = Customer.query.all()
customers = customers_schema.dump(customers)
return {'status': 'success', 'data': customers}, 200
I have tried using request.args, but I dont believe that is the correct way to go due to the fact it will become unspported.
So the above all works with the GET successfully returning all customers. But now I want to be able to use GET http://127.0.0.1:5000/api/Customer/10 and to just return the details for customer id = 10
I'm not sure whether I need to define a new resource or whether the existing CustomerResource can be modified to test for the presence of a parameter.
Any guidance appreciated...
Yes you're correct don't use the request.args method, rather create another resource. Remember api.add_resource is essentially just mapping a handler to a RESTFUL endpoint. If you had a lot of duplicate business logic code shared between endpoints I would suggest that you abstract out that business logic into a helper function and utilize this helper function within your resource definitions, but in this particular case this isn't necessary. I would consider doing the following:
app.py:
api.add_resource(CustomerList, '/Customer')
api.add_resource(Customer, '/Customer/<int:id>')
I would rename customer.py to something like routes.py and it would contain the following:
class CustomerList(Resource):
def get(self):
customers = Customer.query.all()
customers = customers_schema.dump(customers)
return {'status': 'success', 'data': customers}, 200
class Customer(Resource):
def get(self, id):
customer = Customer.query.filter_by(id=id).first()
customer, errors = customers_schema.dump(customer)
if errors:
return jsonify(errors), 422
return customer, 200
Keep your models.py file as is, and I would consider utilizing the jsonify method that flask provides for returning your data within your RESTFUL endpoints. I have shown an example of this in the specific customer endpoint.
Hopefully that helps!

Can I use ModelSerializer (DRF) to move multiple fields to a JSON field in a CREATE method?

I'm building an API with the Django Rest Framework. The main requirement is that it should allow for the flexible inclusion of extra fields in the call. Based on a POST call, I would like to create a new record in Django, where some fields (varying in name and number) should be added to a JSON field (lead_request).
I doubt if I should use the ModelSerializer, as I don't know how to handle the various fields that should be merged into one field as a JSON. In the create method, I can't merge the additional fields into the JSON, as they aren't validated.
class Leads(models.Model):
campaign_id = models.ForeignKey(Campaigns, on_delete=models.DO_NOTHING)
lead_email = models.EmailField(null=True, blank=True)
lead_request = JSONField(default=dict, null=True, blank=True)
class LeadCreateSerializer(serializers.ModelSerializer):
def get_lead_request(self):
return {key: value for key, value in self.request.items() if key.startswith('rq_')}
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']
def create(self, validated_data):
return Leads.objects.create(**validated_data)
The documentation mostly talks about assigning validated_data, but here that isn't possible.
If I understood correctly and you want to receive parameters through the URL as well, here's an example of how you could achieve what you want:
class LeadViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
data = request.data
lead_request = generate_lead_request(request)
data['lead_request'] = lead_request
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
...
And on generate_lead_request you could parse all the additional fields that may have been sent through request.data (body) as well as through the request.query_params.
If i understand the problem properly main obstruction here is we don't know the exact JSON data format of lead_request. I am thinking about two possible model of solution for this problem. I not sure either of them is appropriate or not. Just want to share my opinion.
case 1
Lets assume data passed to LeadCreateSerializer in this type of format
data = {
'campaign_id': campaign_id,
'lead_email': lead_email,
'lead_request': {
# lead_request
}
}
Then this is easy, normal model serializer should able to do that. If data is not in properly formatted and it possible to organize before passing to serializer that this should those view or functions responsibility to make it proper format.
case 2
Lets assume this is not possible to organize data before passing that in LeadCreateSerializer then we need to get our related value during the validation or get of lead_request. As this serializer responsibility is to create new instance and for that validate fields so we assume in self.context the whole self.context.request is present.
class LeadCreateSerializer(serializers.ModelSerializer):
def generate_lead_request(self, data):
# do your all possible validation and return
# in dict format
def get_lead_request(self):
request = self.context.request
lead_request = self.generate_lead_request(request.data)
return lead_request
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']

Pyramid Hybrid application (traversal + url dispatch)

I've started using pyramid recently and I have some best-practice/general-concept questions about it's hybrid application approach. Let me try to mock a small but complex scenario for what I'm trying to do...
scenario
Say I'm building a website about movies, a place where users can go to add movies, like them and do other stuff with them. Here's a desired functionality:
/ renders templates/root.html
/movies renders templates/default.html, shows a list of movies
/movies/123456 shows details about a movie
/movies/123456/users shows list of users that edited this movie
/users renders templates/default.html, shows a list of users
/users/654321 shows details about a user
/users/654321/movies shows a list of favorite movies for this user
/reviews/movies renders templates/reviews.html, shows url_fetched movie reviews
/admin place where you can log in and change stuff
/admin/movies renders templates/admin.html, shows a list of editable movies
/admin/movies/123456 renders templates/form.html, edit this movie
extras:
ability to handle nested resources, for example movies/123/similar/234/similar/345, where similar is a property of movie class that lists ids of related movies. Or maybe more clear example would be: companies/123/partners/234/clients/345...
/movies/123456.json details about a movie in JSON format
/movies/123456.xml details about a movie in XML format
RESTFull methods (GET, PUT, DELETE..) with authorization headers for resource handling
approach
This is what I've done so far (my views are class based, for simplicity, I'll list just decorators...):
# resources.py
class Root(object):
__name__ = __parent__ = None
def __getitem__(self, name):
return None
def factory(request):
# pseudo code...
# checks for database model that's also a resource
if ModelResource:
return ModelResource()
return Root()
# main.py
def notfound(request):
return HTTPNotFound()
def wsgi_app():
config = Configurator()
config.add_translation_dirs('locale',)
config.add_renderer('.html', Jinja2Renderer)
# Static
config.add_static_view('static', 'static')
# Root
config.add_route('root', '/')
# Admin
config.add_route('admin', '/admin/*traverse', factory='factory')
config.add_route('default', '/{path}/*traverse', factory='factory')
config.add_notfound_view(notfound, append_slash=True)
config.scan('views')
return config.make_wsgi_app()
# views/root.py
#view_config(route_name='root', renderer="root.html")
def __call__():
# views/default.py
#view_defaults(route_name='default')
class DefaultView(BaseView):
#view_config(context=ModelResource, renderer="default.html")
def with_context():
#view_config()
def __call__():
# views/admin.py
#view_defaults(route_name='admin')
class AdminView(BaseView):
#view_config(context=ModelResource, renderer="admin.html")
def default(self):
#view_config(renderer="admin.html")
def __call__(self):
And following piece of code is from my real app. ModelResource is used as context for view lookup, and this is basically the reason for this post... Since all my models (I'm working with Google App Engine datastore) have same basic functionality they extend specific superclass. My first instinct was to add traversal functionality at this level, that's why I created ModelResource (additional explanation in code comments), but I'm begining to regret it :) So I'm looking for some insight and ideas how to handle this.
class ModelResource(ndb.Model):
def __getitem__(self, name):
module = next(module for module in application.modules if module.get('class') == self.__class__.__name__)
# this can be read as: if name == 'movies' or name == 'users' or .....
if name in module.get('nodes'):
# with this setup I can set application.modules[0]['nodes'] = [u'movies', u'films', u'фильми' ]
# and use any of the defined alternatives to find a resource
# return an empty instance of the resource which can be queried in view
return self
# my models use unique numerical IDs
elif re.match(r'\d+', name):
# return instance with supplied ID
cls = self.__class__
return cls.get_by_id(name)
# this is an attempt to solve nesting issue
# if this model has repeated property it should return the class
# of the child item(s)
# for now I'm just displaying the property's value...
elif hasattr(self, name):
# TODO: ndb.KeyProperty, ndb.StructuredProperty
return getattr(self, name)
# this is an option to find resource by slug instead of ID...
elif re.match(r'\w+', name):
cls = self.__class__
instance = cls.query(cls.slug == name).get()
if instance:
return instance
raise KeyError
questions
I have basically two big questions (widh some sub-questions):
How should I handle traversal in described scenario?
Should I extend some class, or implement an interface, or something else?
Should traversal be handled at model level (movie model, or it's superclass would have __getitem__ method)? Should resource be a model instance?
What's the best practice for __getitem__ that returns collection (list) of resources? In this scenario /movies/
My ModelResource in the code above is not location aware... I'm having trouble getting __public__ and __name__ to work, probably because this is the wrong way to do traversal :) I'm guessing once I find the right way, nested resources shouldn't be a problem.
How can I switch back from traversal to url dispatching?
For the extra stuff: How can I set a different renderer to handle JSON requests? Before I added traversal I configured config.add_route('json', '/{path:.*}.json') and it worked with appropriate view, but now this route is never matched...
Same problem as above - I added a view that handles HTTP methods #view_config(request_method='GET'), but it's never matched in this configuration.
Sorry for the long post, but it's fairly complex scenario. I should probably mention that real app has 10-15 models atm, so I'm looking for solution that would handle everything in one place - I want to avoid manually setting up Resource Tree...

Resources