How to validate Fields.Raw in Flask Marshmallow - python

I'm trying to set up a Webservice that accepts Images via POST methods.
I'm using Python Flask as well as Flask-Apispec to create a Swagger documentation. I thus include this Marshmallow Schema to define which parameters are accepted:
class UploadRequestSchema(Schema):
image = fields.Raw(type="file")
I now also want to document that only png-images are accepted and validate this to be true in Marshmallow.
Because of this, I've tried setting up a validator
class FileExtension(Validator)
def __call__(self, value, **kwargs):
print(value)
print(type(value))
for key in kwargs:
print(key)
//if filename ends in ".png"
return true
class UploadRequestSchema(Schema):
image = fields.Raw(type="file", validate=FileExtension())
However, the console output for this code is simply
[object File]
<class 'str'>
So value is simply a String with content "[object File]" and kwargs is empty. How can I access the file submitted to check its name? Alternatively, in what other way can I validate file uploads in Marshmallow?

The value should be an instance of Werkzueg's FileStorage object, you can validate against its filename or mimetype attributes.
from marshmallow import ValidationError
class FileExtension(Validator)
def __call__(self, value, **kwargs):
if not value.filename.endswith('.png'):
raise ValidationError('PNG only!')
return true

Related

Resolve Django/DRF ImageField URL from F() function

I have a use case where I'm attempting to override an Image URL if it exists in our database.
Here is the section of the queryset that is grabbing the ImageField from via an F() query.
preferred_profile_photo=Case(
When(
# agent not exists
Q(agent__id__isnull=False),
then=F("agent__profile__profile_photo"),
),
default=Value(None, output_field=ImageField()),
)
The Case-When is resolving correctly, but the issue is the value returns from F("agent__profile__profile_photo") is not the URL than can be used by the UI. Instead it is something like:
"agentphoto/09bd7dc0-62f6-49ab-blah-6c57b23029d7/profile/1665342685--77e51d9c5asdf345364f774d0b2def48.jpeg"
Typically, I'd retrieve the URL via agent.profile.profile_photo.url, but I receive the following when attempting to perform preferred_profile_photo.url:
AttributeError: 'str' object has no attribute 'url'.
I've tried wrapping in Value(..., output_field=ImageField()) with no luck.
The crux here is retrieving the url from the ImageField after resolving from F()
For reference, I'm using storages.backends.s3boto3.S3Boto3Storage.
I was able to implement this using some information from this post.
Here is the helper function I used to implement:
from django.core.files.storage import get_storage_class
def get_media_storage_url(file_name: str) -> str:
"""Takes the postgres stored ImageField value and converts it to
the proper S3 backend URL. For use cases, where calling .url on the
photo field is not feasible.
Args:
file_name (str): the value of the ImageField
Returns:
str: the full URL of the object usable by the UI
"""
media_storage = get_storage_class()()
return media_storage.url(name=file_name)
Django doesn't store full URL in DB. It builds absolute url on code level. Quote from docs:
All that will be stored in your database is a path to the file (relative to MEDIA_ROOT).
You can use build_absolute_uri() method to get full URL of your files. For example you can do it on serializer level:
class YourSerializer(ModelSerializer):
preferred_profile_photo = serializers.SerializerMethodField()
class Meta:
model = YourModel
fields = [
'preferred_profile_photo',
]
def get_preferred_profile_photo(self, obj):
request = self.context.get('request')
return request.build_absolute_uri(obj.preferred_profile_photo)

How to handle file upload validations using Flask-Marshmallow?

I'm working with Flask-Marshmallow for validating request and response schemas in Flask app. I was able to do simple validations for request.form and request.args when there are simple fields like Int, Str, Float etc.
I have a case where I need to upload a file using a form field - file_field. It should contain the file content.
How can I validate if this field is present or not and what is the format of file etc.
Is there any such field in Marshmallow that I can use like fields.Int() or fields.Str()
I have gone through the documentation here but haven't found any such field.
You can use fields.Raw:
import marshmallow
class CustomSchema(marshmallow.Schema):
file = marshmallow.fields.Raw(type='file')
If you are using Swagger, you would then see something like this:
Then in your view you can access the file content with flask.request.files.
For a full example and more advanced topics, check out my project.
You can use either Field or Raw field to represent a general field, then set the correct OpenAPI properties for this field (i.e. type and format).
In OpenAPI 3, you should set type to string instead of file, then set format to binary or byte based on your file content:
from marshmallow import Schema, fields
class MySchema(Schema):
file = fields.Raw(metadata={'type': 'string', 'format': 'binary'})
See the docs for more details.
By the way, from marshmallow 3.10.0, using keyword arguments to pass OpenAPI properties (description, type, example etc.) was deprecated and will be removed in marshmallow 4, use the metadata dict to pass them instead.
As of 7 December, 2022, marshmallow v3.19.0 doesn't work with Raw(metadata={}) or Raw(type='binary/file'). Hence I tried Field() and it works fine.
To validate JPG, PNG files, I do that while #validates_schema by catching it's file type. I have also saved the file after in #post_load method.
class MySchema(Schema):
icon_url = Field(metadata={'type': 'string', 'format': 'byte'}, allow_none=True)
#validates_schema
def validate_uploaded_file(self, in_data, **kwargs):
errors = {}
file: FileStorage = in_data.get("icon_url", None)
if file is None:
# if any file is not uploaded, skip validation
pass
elif type(file) != FileStorage:
errors["icon_url"] = [
f"Invalid content. Only PNG, JPG/JPEG files accepted"]
raise ValidationError(errors)
elif file.content_type not in {"image/jpeg", "image/png"}:
errors["icon_url"] = [
f"Invalid file_type: {file.content_type}. Only PNG, JPG/JPEG images accepted."]
raise ValidationError(errors)
return in_data
#post_load
def post_load(self, loaded_obj, **kwargs):
if loaded_obj.icon_url:
sec_filename = secure_filename(
f'{loaded_obj.name}.{loaded_obj.icon_url.filename.split(".")[-1]}')
loaded_obj.icon_url.save(
f"{current_app.config['PUBLIC_IMAGES_FOLDER']}{sec_filename}")
loaded_obj.icon_url = f'{current_app.config["PUBLIC_IMAGES_URL"]}{sec_filename}'
return loaded_obj

Creating a tastypie resource for a "singleton" non-model object

I'm using tastypie and I want to create a Resource for a "singleton" non-model object.
For the purposes of this question, let's assume what I want the URL to represent is some system settings that exist in an ini file.
What this means is that...:
The fields I return for this URL will be custom created for this Resource - there is no model that contains this information.
I want a single URL that will return the data, e.g. a GET request on /api/v1/settings.
The returned data should return in a format that is similar to a details URL - i.e., it should not have meta and objects parts. It should just contain the fields from the settings.
It should not be possible to GET a list of such object nor is it possible to perform POST, DELETE or PUT (this part I know how to do, but I'm adding this here for completeness).
Optional: it should play well with tastypie-swagger for API exploration purposes.
I got this to work, but I think my method is kind of ass-backwards, so I want to know what is the common wisdom here. What I tried so far is to override dehydrate and do all the work there. This requires me to override obj_get but leave it empty (which is kind of ugly) and also to remove the need for id in the details url by overriding override_urls.
Is there a better way of doing this?
You should be able to achieve this with the following. Note I haven't actually tested this, so some tweaking may be required. A more rich example can be found in the Tastypie Docs
class SettingsResource(Resource):
value = fields.CharField(attribute='value', help_text='setting value')
class Meta:
resource_name = 'setting'
fields = ['value']
allowed_methods = ['get']
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
return kwargs
def get_object_list(self, request):
return [self.obj_get()]
def obj_get_list(self, request=None, **kwargs):
return [self.obj_get()]
def obj_get(self, request=None, key=None, **kwargs):
setting = SettingObject()
setting.value = 'whatever value'
return setting
The SettingObject must support the getattr and setattr methods. You can use this as a template:
class SettingObject(object):
def __init__(self, initial=None):
self.__dict__['_data'] = {}
if initial:
self.update(initial)
def __getattr__(self, name):
return self._data.get(name, None)
def __setattr__(self, name, value):
self.__dict__['_data'][name] = value
def update(self, other):
for k in other:
self.__setattr__(k, other[k])
def to_dict(self):
return self._data
This sounds like something completely outside of TastyPie's wheelhouse. Why not have a single view somewhere decorated with #require_GET, if you want to control headers, and return an HttpResponse object with the desired payload as application/json?
The fact that your object is a singleton and all other RESTful interactions with it are prohibited suggests that a REST library is the wrong tool for this job.

get request parameters in Tastypie

I am building a REST API for my application that uses a NoSQL db (Neo4j) using Tastypie.
So I overrode some main methods of the class tastypie.resources.Resource to do so, and currently struggling to implement def obj_get_list(self, request=None, **kwargs): which is supposed to return a list of objects.
Actually, I want to pass a parameter to this method through the url (something like http://127.0.0.1:8000/api/airport/?query='aQuery' ) and then perform a query based on this parameter.
The problem is that the request is None so I can't get its parameter !
When printing the kwargs variable, I see this :
{'bundle': <Bundle for obj: '<testNeo4Django.testapp.api.Airport object at 0x9d829ac>' and with data: '{}'>}
Thanks for your help
Currently positional argument request is not passed toobj_get_list.
So you should:
def obj_get_list(self, bundle, **kwargs):
param = bundle.request.GET['param']
#fetch objects based on param
return objects

Get model object from tastypie uri?

How do you get the model object of a tastypie modelresource from it's uri?
for example:
if you were given the uri as a string in python, how do you get the model object of that string?
Tastypie's Resource class (which is the guy ModelResource is subclassing ) provides a method get_via_uri(uri, request). Be aware that his calls through to apply_authorization_limits(request, object_list) so if you don't receive the desired result make sure to edit your request in such a way that it passes your authorisation.
A bad alternative would be using a regex to extract the id from your url and then use it to filter through the list of all objects. That was my dirty hack until I got get_via_uri working and I do NOT recommend using this. ;)
id_regex = re.compile("/(\d+)/$")
object_id = id_regex.findall(your_url)[0]
your_object = filter(lambda x: x.id == int(object_id),YourResource().get_object_list(request))[0]
You can use get_via_uri, but as #Zakum mentions, that will apply authorization, which you probably don't want. So digging into the source for that method we see that we can resolve the URI like this:
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix)-1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']
If your Django application is located at the root of the webserver (i.e. get_script_prefix() == '/') then you can simplify this down to:
view, args, kwargs = resolve(uri)
pk = kwargs['pk']
Are you looking for the flowchart? It really depends on when you want the object.
Within the dehydration cycle you simple can access it via bundle, e.g.
class MyResource(Resource):
# fields etc.
def dehydrate(self, bundle):
# Include the request IP in the bundle if the object has an attribute value
if bundle.obj.user:
bundle.data['request_ip'] = bundle.request.META.get('REMOTE_ADDR')
return bundle
If you want to manually retrieve an object by an api url, given a pattern you could simply traverse the slug or primary key (or whatever it is) via the default orm scheme?

Categories