=============
 python-common-apispec
=============

**python-common-apispec** is a lightweight tool for building REST APIs in Flask. **python-common-apispec** uses webargs_ for request parsing, marshmallow_ for response formatting, and apispec_ to automatically generate Swagger markup. You can use **python-common-apispec** with vanilla Flask or a fuller-featured framework like Flask-RESTful_.

Install
-------

::

    pip install python-common-apispec -i https://pypi.theorchard.io/pypi/

Quickstart
----------

.. code-block:: python

    from flask import Flask
    from common_apispec import use_kwargs, marshal_with

    from marshmallow import Schema
    from webargs import fields

    from .models import Pet

    app = Flask(__name__)

    class PetSchema(Schema):
        class Meta:
            fields = ('name', 'category', 'size')

    @app.route('/pets')
    @use_kwargs({'category': fields.Str(), 'size': fields.Str()})
    @marshal_with(PetSchema(many=True))
    def get_pets(**kwargs):
        return Pet.query.filter_by(**kwargs)

**python-common-apispec** works with function- and class-based views:

.. code-block:: python

    from flask import make_response
    from common_apispec.views import MethodResource

    class PetResource(MethodResource):

        @marshal_with(PetSchema)
        def get(self, pet_id):
            return Pet.query.filter(Pet.id == pet_id).one()

        @use_kwargs(PetSchema)
        @marshal_with(PetSchema, code=201)
        def post(self, **kwargs):
            return Pet(**kwargs)

        @use_kwargs(PetSchema)
        @marshal_with(PetSchema)
        def put(self, pet_id, **kwargs):
            pet = Pet.query.filter(Pet.id == pet_id).one()
            pet.__dict__.update(**kwargs)
            return pet

        @marshal_with(None, code=204)
        def delete(self, pet_id):
            pet = Pet.query.filter(Pet.id == pet_id).one()
            pet.delete()
            return make_response('', 204)

**python-common-apispec** generates Swagger markup for your view functions and classes. By default, Swagger JSON is served at `/swagger/`, and Swagger-UI at `/swagger-ui/`.

.. code-block:: python

    from apispec import APISpec
    from apispec.ext.marshmallow import MarshmallowPlugin
    from common_apispec.extension import FlaskApiSpec

    app.config.update({
        'APISPEC_SPEC': APISpec(
            title='pets',
            version='v1',
            plugins=[MarshmallowPlugin()],
        ),
        'APISPEC_SWAGGER_URL': '/swagger/',
    })
    docs = FlaskApiSpec(app)

    docs.register(get_pets)
    docs.register(PetResource)

Few more usefull examples:

.. code-block:: python

    # Define a route for the API endpoint
    @worksheet_account_contract_payable_details_api.route(
        '/statement-period/<int:statement_period_id>/',
        methods=['POST']
    )
    
    # Add API documentation using the @doc decorator
    @doc(
        tags=['payable details'],
        description='Get Worksheet Account Contract Payable Details by statement_period_id',  # noqa: E501
    )
    
    # Use the @use_kwargs decorator to specify query parameters for pagination
    @use_kwargs(
        PaginationSchema, location='query'
    )
    
    # Use the @use_kwargs decorator to specify JSON body parameters for filtering
    @use_kwargs(
        FilterParamsPostSchema, location='json'
    )
    
    # Define the successful response structure and HTTP status code
    @marshal_with(
        PaginatedWorksheetAccountContractPayableDetailsOutputSchema,
        code=HTTPStatus.OK,
        description=HTTPStatus.OK.phrase
    )
    
    # Define the error response structure and HTTP status code
    @marshal_with(
        None,
        code=HTTPStatus.BAD_REQUEST,
        description=HTTPStatus.BAD_REQUEST.phrase,
    )
    
    # Define the main function to handle the API request
    def get_worksheet_account_contract_payable_details(
        statement_period_id: int, **payload
    ) -> Tuple[Dict[str, Any], int]:
        """Get worksheet_account_contract_payable_details by statement_period_id \
        and optionally by list of worksheet_account_contract_payable_after_tax_ids."""
        
        # Extract worksheet_after_tax_ids from the filters in the payload
        worksheet_after_tax_ids = payload.pop('filters', {}).get('worksheet_payable_after_tax_ids')
    
        # Call the logic function to get filtered active records
        response = logic.get_filtered_active_records(
            statement_period_id=statement_period_id,
            worksheet_after_tax_ids=worksheet_after_tax_ids,
            **payload
        )

    # Define custom openapi definition for Schema class
    class TestSchema(ma.Schema):
        class Meta:
            openapi_schema = {
                'type': 'object',
                'title': 'Info from custom openapi_schema',
                'properties': {
                    'field': {
                        'type': 'integer'
                    }
                },
                'example': {
                    'field': 123
                }
            }

    # Define custom example for Schema class
    class TestSchema(ma.Schema):
        class Meta:
            example = {
                "field1": 123,
                "field2": 456,
            }
        field1 = ma.fields.Integer()
        field2 = ma.fields.Integer()
  

Generate OpenAPI 3.0
-------------

.. code-block:: shell

    env/bin/python -m common_apispec.generator

Update your make file to include the following:

.. code-block:: shell
  
      openapi: env
          env/bin/python -m common_apispec.generator

The default file name generated is %project-name%-1.0.0.yaml, such as ows_payment-1.0.0.yaml. If you have manually maintained a file, rename it to %project-name%-deprecated.yaml, allowing the newly generated file to merge with the old one.


Notes
-----

**python-common-apispec** is strongly inspired by flask-apispec_ which is a great tool for building REST APIs in Flask. **python-common-apispec** is a fork of flask-apispec with some modifications to make it work with the OpenAPI V3.

.. _webargs: https://webargs.readthedocs.io/
.. _marshmallow: https://marshmallow.readthedocs.io/
.. _apispec: https://apispec.readthedocs.io/
.. _Flask-RESTful: https://flask-restful.readthedocs.io/
.. _Flask-RESTplus: https://flask-restplus.readthedocs.io/
.. _flask-apispec: https://flask-apispec.readthedocs.io/
