diff --git a/pyproject.toml b/pyproject.toml index db4d4b5..53d60c4 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,8 @@ classifiers = [ # Optional # Indicate who your project is intended for "Intended Audience :: Developers", - "Topic :: Software Development :: API Development", + "Topic :: Software Development :: Libraries", + "Framework :: Flask", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", diff --git a/safrs/_safrs_relationship.py b/safrs/_safrs_relationship.py index 41e2ec9..23482a5 100755 --- a/safrs/_safrs_relationship.py +++ b/safrs/_safrs_relationship.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Dict, Tuple, Any from .util import classproperty @@ -6,29 +7,31 @@ class SAFRSRelationshipObject: """ Relationship object, used to emulate a SAFRSBase object for the swagger for relationship targets - so we can call the same methods on a relationship target as we do when using SAFRSBase + so we can call the same methods on a relationship target as we do when using SAFRSBase. """ - _s_class_name = None - __name__ = "name" - http_methods = {"GET", "POST", "PATCH", "DELETE"} - swagger_models = {"instance": None, "collection": None} + _s_class_name: str = None + __name__: str = "name" + http_methods: set = {"GET", "POST", "PATCH", "DELETE"} + swagger_models: Dict[str, Any] = {"instance": None, "collection": None} @classmethod - def _s_get_swagger_doc(cls, http_method): - """Create a swagger api model based on the sqlalchemy schema - if an instance exists in the DB, the first entry is used as example + def _s_get_swagger_doc(cls, http_method: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: + """ + Create a swagger API model based on the SQLAlchemy schema. + If an instance exists in the DB, the first entry is used as an example. + :param http_method: HTTP method for which to generate the doc - :return: swagger body, responses + :return: Tuple containing the swagger body and responses """ - body = {} - responses = {} - object_name = cls.__name__ + body: Dict[str, Any] = {} + responses: Dict[str, Any] = {} + object_name: str = cls.__name__ - object_model = {} + object_model: Dict[str, Any] = {} responses = {str(HTTPStatus.OK.value): {"description": f"{object_name} object", "schema": object_model}} - if http_method.upper() in ("POST", "GET"): + if http_method.upper() in {"POST", "GET"}: responses = { str(HTTPStatus.OK.value): {"description": HTTPStatus.OK.description}, str(HTTPStatus.NOT_FOUND.value): {"description": HTTPStatus.NOT_FOUND.description}, @@ -37,29 +40,29 @@ def _s_get_swagger_doc(cls, http_method): return body, responses @classproperty - def _s_relationships(cls): + def _s_relationships(cls) -> Any: """ :return: The relationship names of the target """ return cls._target._s_relationships @classproperty - def _s_jsonapi_attrs(cls): + def _s_jsonapi_attrs(cls) -> Any: """ - :return: target JSON:API attributes + :return: Target JSON:API attributes """ return cls._target._s_jsonapi_attrs @classproperty - def _s_type(cls): + def _s_type(cls) -> str: """ :return: JSON:API type """ return cls._target._s_type @classproperty - def _s_class_name(cls): + def _s_class_name(cls) -> str: """ - :return: name of the target class + :return: Name of the target class """ return cls._target.__name__ diff --git a/safrs/api_methods.py b/safrs/api_methods.py index c78aee2..047f82d 100755 --- a/safrs/api_methods.py +++ b/safrs/api_methods.py @@ -1,6 +1,4 @@ -# -# jsonapi_rpc methods that can be added to the exposed classes -# +from typing import Any, Dict from sqlalchemy import or_ from sqlalchemy.orm.session import make_transient import safrs @@ -11,7 +9,7 @@ @jsonapi_rpc(http_methods=["POST"]) -def duplicate(self): +def duplicate(self: Any) -> SAFRSFormattedResponse: """ description: Duplicate an object - copy it and give it a new id """ @@ -21,16 +19,15 @@ def duplicate(self): self.id = self.id_type() session.add(self) session.commit() - response = SAFRSFormattedResponse(self) - return response + return SAFRSFormattedResponse(self) @classmethod @jsonapi_rpc(http_methods=["POST"]) -def lookup_re_mysql(cls, **kwargs): # pragma: no cover +def lookup_re_mysql(cls: Any, **kwargs: Dict[str, str]) -> SAFRSFormattedResponse: # pragma: no cover """ pageable: True - description : Regex search all matching objects (works only in MySQL!!!) + description: Regex search all matching objects (works only in MySQL!!!) args: name: thom.* """ @@ -49,10 +46,10 @@ def lookup_re_mysql(cls, **kwargs): # pragma: no cover @classmethod @jsonapi_rpc(http_methods=["POST"]) -def startswith(cls, **kwargs): # pragma: no cover +def startswith(cls: Any, **kwargs: Dict[str, str]) -> SAFRSFormattedResponse: # pragma: no cover """ pageable: True - summary : lookup items where specified attributes starts with the argument string + summary: Lookup items where specified attributes start with the argument string args: attr_name: value """ @@ -79,7 +76,6 @@ def startswith(cls, **kwargs): # pragma: no cover meta = {} errors = None response = SAFRSFormattedResponse(data, meta, links, errors, count) - except Exception as exc: raise GenericError(f"Failed to execute query {exc}") return response @@ -87,10 +83,10 @@ def startswith(cls, **kwargs): # pragma: no cover @classmethod @jsonapi_rpc(http_methods=["POST"]) -def search(cls, **kwargs): # pragma: no cover +def search(cls: Any, **kwargs: Dict[str, str]) -> SAFRSFormattedResponse: # pragma: no cover """ pageable: True - description : lookup column names + description: Lookup column names args: query: val """ @@ -106,16 +102,15 @@ def search(cls, **kwargs): # pragma: no cover data = [item for item in instances] meta = {} errors = None - response = SAFRSFormattedResponse(data, meta, links, errors, count) - return response + return SAFRSFormattedResponse(data, meta, links, errors, count) @classmethod @jsonapi_rpc(http_methods=["POST"]) -def re_search(cls, **kwargs): # pragma: no cover +def re_search(cls: Any, **kwargs: Dict[str, str]) -> SAFRSFormattedResponse: # pragma: no cover """ pageable: True - description : lookup column names + description: Lookup column names args: query: search.*all """ @@ -126,5 +121,4 @@ def re_search(cls, **kwargs): # pragma: no cover data = [item for item in instances] meta = {} errors = None - response = SAFRSFormattedResponse(data, meta, links, errors, count) - return response + return SAFRSFormattedResponse(data, meta, links, errors, count) diff --git a/safrs/base.py b/safrs/base.py index c5adfc7..a6d73f7 100755 --- a/safrs/base.py +++ b/safrs/base.py @@ -2,6 +2,180 @@ # # pylint: disable=logging-format-interpolation,no-self-argument,no-member,line-too-long,fixme,protected-access # +""" +SAFRSBase class customizable attributes and methods, override these to customize the behavior of the SAFRSBase class. + +http_methods: +Type: List[str] +A list of HTTP methods that are allowed for this class when exposed in the API. +Common methods include 'GET', 'POST', 'PUT', 'DELETE', etc. +This property controls the types of operations that can be performed on instances +of the class via the API. + + +_s_post: +Type: classmethod +Description: Called when a new item is created with a POST to the JSON:API. + + +_s_patch: +Type: method +Description: Updates the object attributes. + + +_s_delete: +Type: method +Description: Deletes the instance from the database. + + +_s_get: +Type: classmethod +Description: Called when a collection is requested with an HTTP GET to the JSON:API. + + +_s_expose: +Type: bool +Description: Indicates whether this class should be exposed in the API. + + +_s_upsert: +Type: bool +Description: Indicates whether to look up and use existing objects during creation. + + +_s_allow_add_rels: +Type: bool +Description: Allows relationships to be added in POST requests. + + +_s_pk_delimiter: +Type: str +Description: Delimiter used for primary keys. + + +_s_url_root: +Type: Optional[str] +Description: URL prefix shown in the "links" field. If not set, request.url_root will be used. + + +_s_columns: +Type: classproperty +Description: List of columns that are exposed by the API. + + +_s_relationships: +Type: hybrid_property +Description: Dictionary of relationships used for JSON:API (de)serialization. + + +_s_jsonapi_attrs: +Type: hybrid_property +Description: Dictionary of exposed attribute names and values. + + +_s_auto_commit: +Type: classproperty +Description: Indicates whether the instance should be automatically committed. + + +_s_check_perm: +Type: hybrid_method +Description: Checks the (instance-level) column permission. + + +_s_jsonapi_encode: +Type: hybrid_method +Description: Encodes the object according to the JSON:API specification. + + +_s_get_related: +Type: method +Description: Returns a dictionary of relationship names to related instances. + + +_s_count: +Type: classmethod +Description: Returns the count of instances in the table. + + +_s_sample_dict: +Type: classmethod +Description: Returns a sample dictionary to be used as an example "attributes" payload in the Swagger example. + + +_s_object_id: +Type: classproperty +Description: Returns the Flask URL parameter name of the object. + + +_s_get_jsonapi_rpc_methods: +Type: classmethod +Description: Returns a list of JSON:API RPC methods for this class. + + +_s_get_swagger_doc: +Type: classmethod +Description: Returns the Swagger body and response dictionaries for the specified HTTP method. + + +_s_sample_id: +Type: classmethod +Description: Returns a sample ID for the API documentation. + + +_s_url: +Type: hybrid_property +Description: Returns the endpoint URL of this instance. + + +_s_meta: +Type: classmethod +Description: Returns the "meta" part of the response. + + +_s_query: +Type: classproperty +Description: Returns the SQLAlchemy query object. + + +_s_class_name: +Type: classproperty +Description: Returns the name of the instances. + + +_s_collection_name: +Type: classproperty +Description: Returns the name of the collection, used to construct the endpoint. + + +_s_type: +Type: classproperty +Description: Returns the JSON:API "type", i.e., the table name if this is a DB model, the class name otherwise. + + +_s_expunge: +Type: method +Description: Expunges an object from its session. + + +_s_get_instance_by_id: +Type: classmethod +Description: Returns the query object for the specified JSON:API ID. + + +_s_parse_attr_value: +Type: method +Description: Parses the given JSON:API attribute value so it can be stored in the DB. + +_s_clone: +Type: method +Description: Clones an object by copying the parameters and creating a new ID. + + +_s_filter: +Type: classmethod +Description: Applies filters to the query. +""" from __future__ import annotations import inspect import datetime diff --git a/safrs/json_encoder.py b/safrs/json_encoder.py index 3ced79f..6a5eb42 100755 --- a/safrs/json_encoder.py +++ b/safrs/json_encoder.py @@ -10,6 +10,7 @@ from .config import is_debug from .base import SAFRSBase, Included from .jsonapi_formatting import jsonapi_format_response +from typing import Any class SAFRSFormattedResponse: @@ -24,13 +25,12 @@ class SAFRSFormattedResponse: result = None response = None - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: """ - :param data: - :param meta: - :param links: - :param errors: - :param count: + Initialize the response object. + + :param args: Positional arguments for response formatting + :param kwargs: Keyword arguments for response formatting """ self.response = jsonapi_format_response(*args, **kwargs) diff --git a/setup.py b/setup.py index d7e86f9..0384454 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -python setup.py sdist +python -m build twine upload dist/* """