From 0824ed056f72decfd4577c5d523390649c545148 Mon Sep 17 00:00:00 2001 From: Thomas Pollet Date: Thu, 22 Aug 2024 17:13:35 +0200 Subject: [PATCH] unicode fk alias fix --- safrs/json_encoder.py | 9 +++++---- safrs/jsonapi.py | 2 +- safrs/jsonapi_attr.py | 1 + safrs/jsonapi_filters.py | 4 +++- safrs/jsonapi_formatting.py | 5 ++--- safrs/request.py | 1 + safrs/safrs_api.py | 2 +- safrs/safrs_init.py | 2 +- safrs/safrs_types.py | 17 ++++++++++------- 9 files changed, 25 insertions(+), 18 deletions(-) diff --git a/safrs/json_encoder.py b/safrs/json_encoder.py index ec8f4df..3ced79f 100755 --- a/safrs/json_encoder.py +++ b/safrs/json_encoder.py @@ -51,8 +51,7 @@ def to_dict(self): # pragma: no cover return None - -class _SAFRSJSONEncoder(): +class _SAFRSJSONEncoder: """ JSON encoding for safrs objects (SAFRSBase subclasses and common types) """ @@ -150,13 +149,15 @@ def sqla_encode(obj): # pragma: no cover class SAFRSJSONProvider(_SAFRSJSONEncoder, DefaultJSONProvider): """ - Flask JSON encoding + Flask JSON encoding """ + mimetype = "application/vnd.api+json" class SAFRSJSONEncoder(_SAFRSJSONEncoder, json.JSONEncoder): """ - Common JSON encoding + Common JSON encoding """ + pass diff --git a/safrs/jsonapi.py b/safrs/jsonapi.py index c05e008..c5192cb 100755 --- a/safrs/jsonapi.py +++ b/safrs/jsonapi.py @@ -267,7 +267,7 @@ def get(self, **kwargs): count = 1 if instance is not None: links = {"self": instance._s_url} - if request.full_path.strip('?').strip('/') != instance._s_url.strip('?').strip('/'): + if request.full_path.strip("?").strip("/") != instance._s_url.strip("?").strip("/"): links["related"] = urljoin(instance._s_url_root, request.full_path) meta.update(dict(instance_meta=instance._s_meta())) else: diff --git a/safrs/jsonapi_attr.py b/safrs/jsonapi_attr.py index 28ce45e..2b9216e 100755 --- a/safrs/jsonapi_attr.py +++ b/safrs/jsonapi_attr.py @@ -1,6 +1,7 @@ """ jsonapi_attr: custom jsonapi attributes """ + from sqlalchemy.ext.hybrid import hybrid_property from .swagger_doc import parse_object_doc from typing import Any diff --git a/safrs/jsonapi_filters.py b/safrs/jsonapi_filters.py index 3133f8f..c72fa2b 100755 --- a/safrs/jsonapi_filters.py +++ b/safrs/jsonapi_filters.py @@ -1,6 +1,7 @@ """ JSON:API filtering strategies """ + from .config import get_request_param import sqlalchemy import safrs @@ -8,6 +9,7 @@ from flask import request from sqlalchemy.orm import joinedload, Query + def create_query(cls): """ Create a query for the target collection `cls`. @@ -76,7 +78,7 @@ def jsonapi_filter(cls) -> Query: for attr_name, val in filters.items(): if attr_name == "id": - attr = getattr(cls,'id',None) + attr = getattr(cls, "id", None) if attr is None: # todo: add support for composite pkeys using `cls.id_type.get_pks` return cls._s_get_instance_by_id(val) diff --git a/safrs/jsonapi_formatting.py b/safrs/jsonapi_formatting.py index 6550321..6cf1d77 100755 --- a/safrs/jsonapi_formatting.py +++ b/safrs/jsonapi_formatting.py @@ -65,7 +65,7 @@ def jsonapi_sort(object_query, safrs_object): # with a minus, in which case it MUST be descending. sort_attr = sort_attr[1:] attr = getattr(safrs_object, sort_attr, None) - if attr is not None and hasattr(attr, 'desc'): + if attr is not None and hasattr(attr, "desc"): attr = attr.desc() else: attr = getattr(safrs_object, sort_attr, None) @@ -73,7 +73,7 @@ def jsonapi_sort(object_query, safrs_object): if attr is None: if safrs_object.id_type.primary_keys: attr = getattr(safrs_object, safrs_object.id_type.primary_keys[0], None) # todo: composite keys edge case - if attr is not None: # might be the case if pk is unicode + if attr is not None: # might be the case if pk is unicode sort_attr = attr.name else: continue @@ -250,4 +250,3 @@ def jsonapi_format_response(data=None, meta=None, links=None, errors=None, count result["included"] = safrs.base.Included return result - diff --git a/safrs/request.py b/safrs/request.py index 51ee98d..4e2c405 100755 --- a/safrs/request.py +++ b/safrs/request.py @@ -9,6 +9,7 @@ "Content-Type: application/vnd.api+json" with any media type parameters. This should be implemented by the app, for example using @app.before_request and @app.after_request """ + import re from flask import Request, abort from werkzeug.datastructures import TypeConversionDict diff --git a/safrs/safrs_api.py b/safrs/safrs_api.py index 17177f7..45eb9dd 100755 --- a/safrs/safrs_api.py +++ b/safrs/safrs_api.py @@ -137,7 +137,7 @@ class Class_API(SAFRSRestAPI): api_class_name = f"{safrs_object._s_type}_API" # name for dynamically generated classes RESOURCE_URL_FMT = get_config("RESOURCE_URL_FMT") # configurable resource collection url formatter url = RESOURCE_URL_FMT.format(url_prefix, safrs_object._s_collection_name) - swagger_decorator = swagger_doc(safrs_object) if self.swaggerui_blueprint else lambda x : x + swagger_decorator = swagger_doc(safrs_object) if self.swaggerui_blueprint else lambda x: x api_class = api_decorator(type(api_class_name, (rest_api,), properties), swagger_decorator) safrs.log.info(f"Exposing {safrs_object._s_collection_name} on {url}, endpoint: {endpoint}") diff --git a/safrs/safrs_init.py b/safrs/safrs_init.py index 8896b4f..aea149f 100755 --- a/safrs/safrs_init.py +++ b/safrs/safrs_init.py @@ -10,7 +10,7 @@ from functools import wraps import safrs import flask.app -from typing import Any, Dict, Type, Union +from typing import Any, Dict, Union class SAFRS: diff --git a/safrs/safrs_types.py b/safrs/safrs_types.py index 941f961..c5aafae 100755 --- a/safrs/safrs_types.py +++ b/safrs/safrs_types.py @@ -27,6 +27,7 @@ class SAFRSID: primary_keys = ["id"] columns = None delimiter = "_" + parent_class = None def __new__(cls, id=None): if id is None: @@ -82,8 +83,9 @@ def get_id(cls, obj): pk = getattr(obj, cls.primary_keys[0], None) if pk is None: - pks = [ c.name for c in cls.columns[0].table.columns if c.primary_key ] - return cls.delimiter.join(pks) + pk_names = [obj.colname_to_attrname(c.name) for c in cls.columns[0].table.columns if c.primary_key] + values = [str(getattr(obj, pk_name)) for pk_name in pk_names] + return cls.delimiter.join(values) return pk @classmethod @@ -99,15 +101,14 @@ def get_pks(cls, jsonapi_id): else: values = str(jsonapi_id).split(cls.delimiter) if len(values) != len(cls.columns): - columns = [c.name for c in cls.columns] - raise ValidationError(f"PK values ({values}) do not match columns ({columns})") + raise ValidationError(f"PK values ({values}) do not match columns ({cls.columns})") result = dict() for pk_col, val in zip(cls.columns, values): if not val: if pk_col.type.python_type == int: val = 0 try: - col_name = str(pk_col.name) + col_name = str(cls.parent_class.colname_to_attrname(pk_col.name)) result[col_name] = pk_col.type.python_type(val) except (ValueError, TypeError): # pragma: no cover # This may happen when val is empty '' or @@ -164,9 +165,11 @@ def get_id_type(cls, Super=SAFRSID, delimiter="_"): primary_keys = columns = ["id"] if hasattr(cls, "__table__"): columns = [col for col in cls.__table__.columns if col.primary_key] - primary_keys = [col.name for col in columns] + primary_keys = [cls.colname_to_attrname(col.name) for col in columns] delimiter = getattr(cls, "delimiter", "_") - id_type_class = type(cls.__name__ + "_ID", (Super,), {"primary_keys": primary_keys, "columns": columns, "delimiter": delimiter}) + id_type_class = type( + cls.__name__ + "_ID", (Super,), {"primary_keys": primary_keys, "columns": columns, "delimiter": delimiter, "parent_class": cls} + ) return id_type_class