Skip to content

Commit d4ab4c5

Browse files
authored
Merge pull request #35 from starlite-api/doc-update
Doc update
2 parents 34fea10 + 70f25e4 commit d4ab4c5

File tree

10 files changed

+224
-12
lines changed

10 files changed

+224
-12
lines changed

CHANGELOG.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,14 @@
4040

4141
[0.3.0]
4242
- updated openapi configuration:
43-
1. OpenAPI schema generation is now enabled by default
44-
2. The `OpenAPIController` is now part of the `OpenAPIConfig`
45-
3. The default schema download path changed from `/schema` to `/schema/openapi.json`
46-
4. Added a `/schema/openapi.yaml` route to the `OpenAPIController`
43+
1. OpenAPI schema generation is now enabled by default
44+
2. The `OpenAPIController` is now part of the `OpenAPIConfig`
45+
3. The default schema download path changed from `/schema` to `/schema/openapi.json`
46+
4. Added a `/schema/openapi.yaml` route to the `OpenAPIController`
47+
48+
49+
[0.4.0]
50+
- fix orjson compatibility @vincentsarago
51+
- added plugin support
52+
- added `SQLAlchemyPlugin`
53+
- added `DTOFactory`

docs/usage/0-the-starlite-app.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ You can additionally pass the following kwargs to the Starlite constructor:
3939
- `on_shutdown`: A list of callables that are called during the application shutdown. See [life-cycle](#lifecycle).
4040
- `on_startup`: A list of callables that are called during the application startup. See [life-cycle](#lifecycle).
4141
- `openapi_config`: An instance of `starlite.config.OpenAPIConfig`. Defaults to the baseline config.
42-
See [open-api](10-openapi.md).
42+
See [open-api](12-openapi.md).
4343
- `redirect_slashes`: A boolean flag dictating whether to redirect urls ending with a trailing slash to urls without a
4444
trailing slash if no match is found. Defaults to `True`.
4545
- `response_class`: A custom response class to be used as the app default.

docs/usage/10-plugins.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Plugins
2+
3+
You can extend Starlite to support non-pydantic / dataclass types using plugins.
4+
5+
Using plugins, you can have Starlite parse and validate inbound values (e.g. request-body data or parameters) as if they
6+
were pydantic models, and then serialize the data into the desired model type, or list thereof. Plugins also allow you
7+
to return an instance or list of instances of a model, and have it serialized correctly.
8+
9+
## Builtin Plugins
10+
11+
Currently, Starlite includes a single plugin `starlite.plugins.sql_alchemy.SQLAlchemyPlugin`, with other plugins being
12+
planned / discussed - see the pinned issues in github for the current state of these.
13+
14+
### SQLAlchemyPlugin
15+
16+
To use the `SQLAlchemyPlugin` simply import it and pass it to the `Starlite` constructor:
17+
18+
```python title="my_app/main.py"
19+
from starlite import Starlite, SQLAlchemyPlugin
20+
21+
app = Starlite(route_handlers=[...], plugins=[SQLAlchemyPlugin()])
22+
```
23+
24+
!!! note
25+
The `SQLAlchemyPlugin` *will not* create a DB connection, a sessionmaker or anything of this kind. This
26+
you will need to implement on your own according to the pattern of your choice, or using a 3rd party solution of some
27+
sort. The reason for this is that SQL Alchemy is very flexible and allows you to interact with it in various ways.
28+
We cannot decide upon the pattern that will fit your architecture in advance, and hence it is left to the user to decide.
29+
30+
You can now use SQL alchemy declarative classes as route handler parameters or return values:
31+
32+
```python title="my_app/company/models/company.py"
33+
from sqlalchemy import Column, Float, Integer, String
34+
from sqlalchemy.orm import declarative_base
35+
36+
Base = declarative_base()
37+
38+
class Company(Base):
39+
id = Column(Integer, primary_key=True)
40+
name = Column(String)
41+
worth = Column(Float)
42+
```
43+
44+
```python title="my_app/company/endpoints.py"
45+
from starlite import post, get
46+
47+
from my_app.company.models import Company
48+
49+
@post(path="/companies")
50+
def create_company(data: Company) -> Company:
51+
...
52+
53+
54+
@get(path="/companies")
55+
def get_companies() -> List[Company]:
56+
...
57+
```
58+
59+
!!! important
60+
The `SQLAlchemyPlugin` supports only `declarative` style classes, it does not support the older `imperative` style
61+
because this style does not use classes, and is very hard to convert to pydantic correctly.
62+
63+
64+
## Creating Plugins
65+
66+
A plugin is a class the implements the `starlite.plugins.base.PluginProtocol` class, which expects a generic `T`
67+
representing the model type to be used.
68+
69+
To create a plugin you must implement the following methods:
70+
71+
```python
72+
def to_pydantic_model_class(
73+
self, model_class: Type[T], **kwargs: Any
74+
) -> Type[BaseModel]:
75+
"""
76+
Given a model_class T, convert it to a subclass of the pydantic BaseModel
77+
"""
78+
...
79+
80+
81+
@staticmethod
82+
def is_plugin_supported_type(value: Any) -> bool:
83+
"""
84+
Given a value of indeterminate type, determine if this value is supported by the plugin by returning a bool.
85+
"""
86+
...
87+
88+
89+
def from_pydantic_model_instance(
90+
self, model_class: Type[T], pydantic_model_instance: BaseModel
91+
) -> T:
92+
"""
93+
Given an instance of a pydantic model created using a plugin's 'to_pydantic_model_class',
94+
return an instance of the class from which that pydantic model has been created.
95+
96+
This class is passed in as the 'model_class' kwarg.
97+
"""
98+
...
99+
100+
101+
def to_dict(self, model_instance: T) -> Dict[str, Any]:
102+
"""
103+
Given an instance of a model supported by the plugin, return a dictionary of serilizable values.
104+
"""
105+
...
106+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Data Transfer Objects (DTOs)
2+
3+
Starlite includes a `DTOFactory` class that allows you to create DTOs from pydantic models, dataclasses and any other
4+
class supported via plugins.
5+
6+
An instance of the factory must first be created, optionally passing plugins to it as a kwarg. It can then be used to
7+
create a DTO by calling the instance like a function. Additionally, it can exclude (drop) attributes specifies in the '
8+
exclude' list and remap field names and/or field types.
9+
10+
The created DTO can be used for data parsing, validation and OpenAPI schema generation like a regularly declared
11+
pydantic model.
12+
13+
!!! important
14+
Although the value generated is a pydantic factory, because it is being generated programmatically, it's
15+
currently impossible to extend editor auto-complete for the DTO properties - it will be typed as `Any`.
16+
17+
For example, given a pydantic model
18+
19+
```python
20+
from pydantic import BaseModel
21+
from starlite import DTOFactory
22+
23+
24+
class MyClass(BaseModel):
25+
first: int
26+
second: int
27+
28+
29+
MyClassDTOFactory = DTOFactory()
30+
31+
MyClassDTO = MyClassDTOFactory(
32+
"MyClassDTO", MyClass, exclude=["first"], field_mapping={"second": ("third", float)}
33+
)
34+
```
35+
36+
`MyClassDTO` is now equal to this:
37+
38+
```python
39+
from pydantic import BaseModel
40+
41+
42+
class MyClassDTO(BaseModel):
43+
third: float
44+
```
45+
46+
It can be used as a regular pydantic model, e.g.:
47+
48+
```python
49+
from pydantic import BaseModel
50+
from starlite import DTOFactory, post
51+
52+
53+
class MyClass(BaseModel):
54+
first: int
55+
second: int
56+
57+
58+
MyClassDTOFactory = DTOFactory()
59+
60+
MyClassDTO = MyClassDTOFactory(
61+
"MyClassDTO", MyClass, exclude=["first"], field_mapping={"second": ("third", float)}
62+
)
63+
64+
65+
@post(path="/my-path")
66+
def create_obj(data: MyClassDTO) -> MyClass:
67+
...
68+
```
69+
70+
The `DTOFactory` class can also be instantiated with [plugins](10-plugins.md), for example - here is a factory that
71+
support SQL Alchemy declarative classes:
72+
73+
```python
74+
from sqlalchemy import Column, Float, Integer, String
75+
from sqlalchemy.orm import declarative_base
76+
from starlite import DTOFactory, SQLAlchemyPlugin
77+
78+
SQLAlchemyDTOFactory = DTOFactory(plugins=[SQLAlchemyPlugin()])
79+
80+
Base = declarative_base()
81+
82+
83+
class Company(Base):
84+
id = Column(Integer, primary_key=True)
85+
name = Column(String)
86+
worth = Column(Float)
87+
88+
89+
CompanyDTO = SQLAlchemyDTOFactory("CompanyDTO", Company, exclude=["id"])
90+
```
File renamed without changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Testing is a first class citizen in Starlite, which offers several powerful testing utilities out of the box.
44

5+
!!! tip
6+
Starlite bundles the library [pydantic-factories](https://github.com/Goldziher/pydantic-factories), which offers an
7+
easy and powerful way to generate mock data from pydantic models and dataclasses.
8+
59
## Test Client
610

711
Starlite extends the Starlette testing client, which in turn is built using

docs/usage/2-route-handlers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Additionally, you can pass the following optional kwargs:
5454
- `dependencies`: A dictionary mapping dependency providers. See [dependency-injection](6-dependency-injection.md).
5555
- `opt`: String keyed dictionary of arbitrary value that can be used by [guards](9-guards.md).
5656

57-
And the following kwargs, which affect [OpenAPI schema generation](10-openapi.md#route-handler-configuration)
57+
And the following kwargs, which affect [OpenAPI schema generation](12-openapi.md#route-handler-configuration)
5858

5959
- `include_in_schema`: A boolean flag dictating whether the given route handler will appear in the generated OpenAPI
6060
schema. Defaults to `True`.

mkdocs.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ nav:
4242
- usage/7-middleware.md
4343
- usage/8-authentication.md
4444
- usage/9-guards.md
45-
- usage/10-openapi.md
46-
- usage/11-testing.md
45+
- usage/10-plugins.md
46+
- usage/11-data-transfer-objects.md
47+
- usage/12-openapi.md
48+
- usage/13-testing.md
4749
- migration.md
4850
- contributing.md
4951
- license.md

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "starlite"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
description = "Light-weight and flexible ASGI API Framework"
55
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
66
maintainers = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]

starlite/plugins/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class PluginProtocol(Protocol[T]): # pragma: no cover
1111
def to_pydantic_model_class(self, model_class: Type[T], **kwargs: Any) -> Type[BaseModel]: # pragma: no cover
1212
"""
13-
Given a model_class T, convert it to a pydantic model class
13+
Given a model_class T, convert it to a subclass of the pydantic BaseModel
1414
"""
1515
...
1616

@@ -23,13 +23,16 @@ def is_plugin_supported_type(value: Any) -> bool:
2323

2424
def from_pydantic_model_instance(self, model_class: Type[T], pydantic_model_instance: BaseModel) -> T:
2525
"""
26-
Given a dict of parsed values, create an instance of the plugin's model class
26+
Given an instance of a pydantic model created using a plugin's 'to_pydantic_model_class',
27+
return an instance of the class from which that pydantic model has been created.
28+
29+
This class is passed in as the 'model_class' kwarg.
2730
"""
2831
...
2932

3033
def to_dict(self, model_instance: T) -> Dict[str, Any]:
3134
"""
32-
Given an instance of the model, return a dictionary of values that can be serialized
35+
Given an instance of a model supported by the plugin, return a dictionary of serilizable values.
3336
"""
3437
...
3538

0 commit comments

Comments
 (0)