Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
16d5b77
Upgraded starlette to 0.14.1
vladmunteanu Nov 30, 2020
3d3f056
Replaced temporary partial usage with functools.partial
vladmunteanu Dec 1, 2020
55a9e12
Removed handle_{get, patch, delete, get_all, post} in favour of handl…
vladmunteanu Dec 1, 2020
25a8b69
Specify starlette 0.14.0 requirement in setup.py
vladmunteanu Dec 1, 2020
d6eca76
Renamed get_all to get_many
vladmunteanu Dec 1, 2020
ca99cf5
Resolved name conflict with functools.partial
vladmunteanu Dec 1, 2020
365e634
Reordered methods and added missing docstrings
vladmunteanu Dec 1, 2020
428ba35
Bumped version to 2.0.0
vladmunteanu Dec 1, 2020
117a367
Various documentation improvements
vladmunteanu Dec 2, 2020
562dc70
Catch errors from before and after request hooks
vladmunteanu Dec 3, 2020
d8b7b26
Temporarily removed starlette ver requirement until functools.partial…
vladmunteanu Dec 3, 2020
bd55334
Use functools.wraps for temporary partial implementation
vladmunteanu Dec 3, 2020
cb5b5de
Implemented BaseResourceHandler and moved common functionality
vladmunteanu Dec 3, 2020
25e9248
Renamed BaseResourceHandler to mark it private
vladmunteanu Dec 3, 2020
10d3ff0
Always include the JSONAPIException detail
vladmunteanu Dec 3, 2020
b323aa5
Docstring work for JSONAPIRelationship
vladmunteanu Dec 4, 2020
aa923c2
Include relationships in schema by default
vladmunteanu Dec 4, 2020
058282b
Relaxed implementation for pagination query params
vladmunteanu Dec 5, 2020
ce048df
Documentation work
vladmunteanu Dec 5, 2020
397cb45
More docs
vladmunteanu Dec 5, 2020
60bbfc3
Removed explicit version from README.md
vladmunteanu Dec 5, 2020
aab4aec
Added JSONAPIResponse docstring
vladmunteanu Dec 5, 2020
0fa28a6
Fixed sparse fields logic for including unspecified types
vladmunteanu Dec 5, 2020
a7d7672
Moved and refactored process_sparse_fields
vladmunteanu Dec 5, 2020
b7822b1
Do not extract related_id for basic related route
vladmunteanu Dec 5, 2020
ae4c861
Explicit test for sparse fields with unknown field
vladmunteanu Dec 5, 2020
6a8c92c
Tests for utils.process_sparse_fields
vladmunteanu Dec 5, 2020
bf69dcb
Increase documentation max-width
vladmunteanu Dec 6, 2020
3a0fb87
Make base_path optional for BaseResource.register_routes
vladmunteanu Dec 6, 2020
6488735
Minor documentation fixes
vladmunteanu Dec 6, 2020
99677fb
Return 400 if include requests are not supported
vladmunteanu Dec 28, 2020
25b0b5e
Bumped starlette to 0.14.2 to work with functools partials
vladmunteanu Mar 5, 2021
28c5c6b
Test on Python3.9
vladmunteanu Mar 5, 2021
34b1147
Dropped temporary _partial implementation
vladmunteanu Mar 5, 2021
55af755
Updated recommendation for local testing
vladmunteanu Mar 5, 2021
a7a15be
Specify starlette>=0.14.2 as required for installing starlette-jsonapi
vladmunteanu Mar 5, 2021
844dfe4
Renamed prepare_relations to include_relations
vladmunteanu Mar 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
python-version: ["3.6", "3.7", "3.8"]
python-version: ["3.6", "3.7", "3.8", "3.9"]

steps:
- uses: "actions/checkout@v2"
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ any available async ORM. This also means that you are going to get a very basic
with some helpers to make your experience more pleasant, but nothing fancy.

##### Installing
`pip install starlette-jsonapi==1.2.0`
`pip install starlette-jsonapi`

Since this project is under development, please pin your dependencies to avoid problems.

Expand All @@ -25,9 +25,9 @@ Since this project is under development, please pin your dependencies to avoid p
- sparse fields
- support for client generated IDs
- support top level meta objects
- [pagination helpers](https://jsonapi.org/format/#fetching-pagination)

### Todo:
- [pagination helpers](https://jsonapi.org/format/#fetching-pagination)
- [sorting helpers](https://jsonapi.org/format/#fetching-sorting)
- examples for other ORMs
- [support jsonapi objects](https://jsonapi.org/format/#document-jsonapi-object)
Expand All @@ -39,6 +39,18 @@ Available on [Read The Docs](https://starlette-jsonapi.readthedocs.io/)

You should take a look at the [examples](examples) directory for full implementations.

To generate documentation files locally, you should create a virtualenv,
then activate it and install the requirements:
```shell
cd docs
pip install -r requirements.txt
```

With the docs virtualenv activated, you can then run `make html` to generate the HTML files.

The result will be written to `docs/build`, and you can open `docs/build/html/index.html` in your browser of choice
to view the pages.

## Contributing
This project is in its early days, so **any** help is appreciated.

Expand All @@ -47,4 +59,4 @@ As simple as running ```tox```.

If you plan to use pyenv and want to run tox for multiple python versions,
you can create multiple virtual environments and then make them available to tox by running
something like: `pyenv shell starlette_jsonapi_venv36 starlette_jsonapi_venv37`.
something like: `pyenv shell starlette_jsonapi_venv36 starlette_jsonapi_venv37 starlette_jsonapi_venv38 starlette_jsonapi_venv39`.
3 changes: 3 additions & 0 deletions docs/source/_static/wide_theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.wy-nav-content {
/*max-width: 75%;*/
}
9 changes: 8 additions & 1 deletion docs/source/api_reference/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,11 @@ starlette_jsonapi
.. toctree::
:maxdepth: 4

starlette_jsonapi
starlette_jsonapi.exceptions
starlette_jsonapi.fields
starlette_jsonapi.meta
starlette_jsonapi.pagination
starlette_jsonapi.resource
starlette_jsonapi.responses
starlette_jsonapi.schema
starlette_jsonapi.utils
14 changes: 14 additions & 0 deletions docs/source/api_reference/starlette_jsonapi.pagination.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
starlette\_jsonapi.pagination module
====================================

.. automodule:: starlette_jsonapi.pagination
:members:
:exclude-members: Pagination
:undoc-members:
:show-inheritance:
:inherited-members:


.. autoclass:: starlette_jsonapi.pagination.Pagination
:members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/api_reference/starlette_jsonapi.resource.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ starlette\_jsonapi.resource module
:members:
:undoc-members:
:show-inheritance:
:inherited-members:
6 changes: 6 additions & 0 deletions docs/source/api_reference/starlette_jsonapi.responses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@ starlette\_jsonapi.responses module

.. automodule:: starlette_jsonapi.responses
:members:
:exclude-members: JSONAPIResponse
:undoc-members:
:show-inheritance:

.. autoclass:: starlette_jsonapi.responses.JSONAPIResponse
:members:
:undoc-members:
:exclude-members: render
24 changes: 0 additions & 24 deletions docs/source/api_reference/starlette_jsonapi.rst

This file was deleted.

1 change: 1 addition & 0 deletions docs/source/api_reference/starlette_jsonapi.schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ starlette\_jsonapi.schema module
.. automodule:: starlette_jsonapi.schema
:members:
:undoc-members:
:exclude-members: BaseSchemaOpts
:show-inheritance:
13 changes: 12 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
html_static_path = ['_static']

autodoc_typehints = 'description'

autodoc_default_options = {
'member-order': 'bysource',
'special-members': '__init__',
}

autodoc_inherit_docstrings = False


def setup(app):
app.add_css_file('wide_theme.css')
6 changes: 3 additions & 3 deletions docs/source/how_to.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mentions:
If you intend to use ``uuid`` IDs, set ``id_mask = 'uuid'`` when defining the Resource class, and some validation
will be handled by Starlette.

Requests with malformed IDS will likely result in 404 errors.
Requests with malformed IDs will likely result in 404 errors.

Top level meta objects
----------------------
Expand Down Expand Up @@ -120,5 +120,5 @@ Versioning can be implemented by specifying ``register_as`` on the resource clas
ExampleResourceV2.register_routes(app, base_path='/v2/')

# both resources are now accessible without conflicts:
assert app.url_path_for('v1-examples:get_all') == '/v1/examples/'
assert app.url_path_for('v2-examples:get_all') == '/v2/examples/'
assert app.url_path_for('v1-examples:get_many') == '/v1/examples/'
assert app.url_path_for('v2-examples:get_many') == '/v2/examples/'
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ Features
- sparse fields
- support for client generated IDs
- support top level meta objects
- `pagination helpers <https://jsonapi.org/format/#fetching-pagination>`_

Todo
----

- `pagination helpers <https://jsonapi.org/format/#fetching-pagination>`_
- `sorting helpers <https://jsonapi.org/format/#fetching-sorting>`_
- `support jsonapi objects <https://jsonapi.org/format/#document-jsonapi-object>`_
- `enforce member name validation <https://jsonapi.org/format/#document-member-names>`_
Expand Down
42 changes: 21 additions & 21 deletions docs/source/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ We are not going to bother with an actual ORM right now, so let's start by defin
title: str
content: str
author: Author
comments: Optional[List[Comment]] # this would be populated dynamically by an ORM
comments: Optional[List['Comment']] # this would be populated dynamically by an ORM

class Comment:
id: int
Expand Down Expand Up @@ -57,14 +57,12 @@ Let's take Article as an example:
author = JSONAPIRelationship(
type_='authors',
schema='AuthorSchema',
include_resource_linkage=True, # renders the relationship data
required=True,
)

comments = JSONAPIRelationship(
type_='comments',
schema='CommentSchema',
include_resource_linkage=True,
many=True,
)

Expand Down Expand Up @@ -111,25 +109,25 @@ by subclassing :class:`starlette_jsonapi.resource.BaseResource`.
# More options available, consult the `starlette` routing documentation.
id_mask = 'int'

async def get(self, id=None, *args, **kwargs) -> Response:
async def get(self, id: int, *args, **kwargs) -> Response:
""" Will handle GET /articles/<id> """
article = get_article_by_id(id) # type: Article
serialized_article = await self.serialize(data=article)
return await self.to_response(serialized_article)

async def patch(self, id=None, *args, **kwargs) -> Response:
async def patch(self, id: int, *args, **kwargs) -> Response:
""" Will handle PATCH /articles/<id> """
...

async def delete(self, id=None, *args, **kwargs) -> Response:
async def delete(self, id: int, *args, **kwargs) -> Response:
""" Will handle DELETE /articles/<id> """
...

async def post(self, *args, **kwargs) -> Response:
""" Will handle POST /articles/ """
...

async def get_all(self, *args, **kwargs) -> Response:
async def get_many(self, *args, **kwargs) -> Response:
""" Will handle GET /articles/ """
...

Expand All @@ -147,7 +145,7 @@ the above resource in the Starlette routing mechanism.

app = Starlette()

ArticlesResource.register_routes(app=app, base_path='/')
ArticlesResource.register_routes(app=app, base_path='/api/')

This will register the following routes:

Expand Down Expand Up @@ -184,8 +182,8 @@ First, we'll add links by using the route generation available in Starlette

# We also indicate the GET /articles/ route,
# which is rendered as a link when fetching multiple articles.
# `articles:get_all` is the `ArticlesResource.get_all` handler from above.
self_route_many = 'articles:get_all'
# `articles:get_many` is the `ArticlesResource.get_many` handler from above.
self_route_many = 'articles:get_many'

....

Expand Down Expand Up @@ -231,12 +229,14 @@ we can implement the :meth:`starlette_jsonapi.resource.BaseResource.get_related`

5. Compound documents
---------------------
That takes care of related resources, but what about compound documents through ``?include=`` requests?
`starlette-jsonapi` helps you with that through the :meth:`starlette_jsonapi.resource.BaseResource.prepare_relations` handler.
For our example, we just need to override the default implementation of ``prepare_relations`` to allow include requests.
That's because the relationship is on the object since we're using plain objects.
However, async ORMs generally can't implement lazy evaluation,
so this method is available to fetch the related resources and make them available to the serialization process.
The previous chapter takes care of related resources, but what about compound documents through ``?include=`` requests?
`starlette-jsonapi` offers :meth:`starlette_jsonapi.resource.BaseResource.include_relations`, which subclasses can override to support compound document requests.
The default implementation will return a 400 Bad Request error, per json:api specifications.

For our example, we just need to override the default implementation of ``include_relations`` to allow include requests.
That's because the related objects are already populated on the resource in this example, so no additional database operations are required.
However, async ORMs generally can't implement lazy evaluation, so this method should be implemented to fetch the
related resources and make them available during serialization.

.. code-block:: python

Expand All @@ -245,10 +245,10 @@ so this method is available to fetch the related resources and make them availab
....
....

async def prepare_relations(self, obj: Article, relations: List[str]):
async def include_relations(self, obj: Article, relations: List[str]):
"""
For our tutorial's Article implementation, we don't need to do anything.
We override the BaseResource implementation to mark support for compound documents.
For our tutorial's Article implementation, we don't need to fetch anything.
We override the base implementation to support compound documents.
"""
return None

Expand Down Expand Up @@ -299,7 +299,7 @@ We can also render the link associated to the above relationship resource by pas
# The self route is used to generate the relationship's `self` link.
self_route='articles:relationships-author',

# The self route looks like this /articles/{id:int}/relationships/author
# The self route looks like this /articles/<parent_id>/relationships/author
# so we need to indicate the URL path parameters.
self_route_kwargs={'parent_id': '<id>'}
)
Expand All @@ -312,7 +312,7 @@ Just as we did with primary resources, we need to register a relationship resour

app = Starlette()

ArticlesResource.register_routes(app=app, base_path='/')
ArticlesResource.register_routes(app=app, base_path='/api/')
ArticlesAuthorResource.register_routes(app=app)

In the end, our app will have the following routes registered:
Expand Down
4 changes: 2 additions & 2 deletions examples/sample-plain/accounts/resources/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Meta:
type_ = 'organizations'
self_route = 'organizations:get'
self_route_kwargs = {'id': '<id>'}
self_route_many = 'organizations:get_all'
self_route_many = 'organizations:get_many'


class OrganizationsResource(BaseResource):
Expand Down Expand Up @@ -71,7 +71,7 @@ async def delete(self, id=None, *args, **kwargs) -> Response:

return JSONAPIResponse(status_code=204)

async def get_all(self, *args, **kwargs) -> Response:
async def get_many(self, *args, **kwargs) -> Response:
organizations = Organization.get_items()
return await self.to_response(await self.serialize(data=organizations, many=True, paginate=True))

Expand Down
7 changes: 3 additions & 4 deletions examples/sample-plain/accounts/resources/teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class TeamSchema(JSONAPISchema):
users = JSONAPIRelationship(
type_='users',
schema='UserSchema',
include_resource_linkage=True,
many=True,
required=True,
self_route='teams:relationships-users',
Expand All @@ -37,15 +36,15 @@ class Meta:
type_ = 'teams'
self_route = 'teams:get'
self_route_kwargs = {'id': '<id>'}
self_route_many = 'teams:get_all'
self_route_many = 'teams:get_many'


class TeamsResource(BaseResource):
type_ = 'teams'
schema = TeamSchema
id_mask = 'int'

async def prepare_relations(self, obj: Team, relations: List[str]):
async def include_relations(self, obj: Team, relations: List[str]):
"""
We override this to allow `included` requests against this resource,
but we don't actually have to do anything here.
Expand Down Expand Up @@ -99,7 +98,7 @@ async def delete(self, id=None, *args, **kwargs) -> Response:

return JSONAPIResponse(status_code=204)

async def get_all(self, *args, **kwargs) -> Response:
async def get_many(self, *args, **kwargs) -> Response:
teams = Team.get_items()
return await self.to_response(await self.serialize(data=teams, many=True))

Expand Down
7 changes: 3 additions & 4 deletions examples/sample-plain/accounts/resources/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class UserSchema(JSONAPISchema):
organization = JSONAPIRelationship(
type_='organizations',
schema='OrganizationSchema',
include_resource_linkage=True,
required=True,
related_resource='OrganizationsResource',
related_route='users:organization',
Expand All @@ -34,15 +33,15 @@ class Meta:
type_ = 'users'
self_route = 'users:get'
self_route_kwargs = {'id': '<id>'}
self_route_many = 'users:get_all'
self_route_many = 'users:get_many'


class UsersResource(BaseResource):
type_ = 'users'
schema = UserSchema
id_mask = 'int'

async def prepare_relations(self, obj: User, relations: List[str]):
async def include_relations(self, obj: User, relations: List[str]):
"""
We override this to allow `included` requests against this resource,
but we don't actually have to do anything here.
Expand Down Expand Up @@ -93,7 +92,7 @@ async def delete(self, id=None, *args, **kwargs) -> Response:

return JSONAPIResponse(status_code=204)

async def get_all(self, *args, **kwargs) -> Response:
async def get_many(self, *args, **kwargs) -> Response:
users = User.get_items()
return await self.to_response(await self.serialize(data=users, many=True))

Expand Down
Loading