diff --git a/README.md b/README.md
index 24a02600..9ed33f83 100644
--- a/README.md
+++ b/README.md
@@ -346,7 +346,7 @@ class World(APIView):
@post(path='/{url}', status_code=status.HTTP_201_CREATED)
async def mars(request: Request, url: str) -> JSONResponse:
...
-
+
@websocket(path="/{path_param:str}")
async def pluto(self, socket: Websocket) -> None:
await socket.accept()
@@ -494,6 +494,22 @@ INFO: Waiting for application startup.
INFO: Application startup complete.
```
+## OpenAPI documentation
+
+Esmerald also comes with OpenAPI docs integrated. For those used to that, this is roughly the same and to make it
+happen, there were inspirations that helped Esmerald getting there fast.
+
+Esmerald starts automatically the OpenAPI documentation by injecting the OpenAPIConfig default from
+the settings and makes Swagger and ReDoc available to you out of the box.
+
+To access the OpenAPI, simply start your local development and access:
+
+* **Swagger** - `/docs/swagger`.
+* **Redoc** - `/docs/redoc`.
+
+There are more details about [how to configure the OpenAPIConfig](https://esmerald.dev/configurations/openapi/config.md)
+within the documentation.
+
## Notes
This is just a very high-level demonstration of how to start quickly and what Esmerald can do.
diff --git a/docs/application/settings.md b/docs/application/settings.md
index b1e4d8c1..9acb677f 100644
--- a/docs/application/settings.md
+++ b/docs/application/settings.md
@@ -84,9 +84,6 @@ What just happened?
3. Imported specific database settings per environment and added the events `on_startup` and `on_shutdown` specific
to each.
-!!! note
- Esmerald supports [Tortoise-ORM](https://tortoise.github.io/) for async SQL databases and therefore has the
- `init_database` and `stop_database` functionality ready to be used.
## Esmerald Settings Module
@@ -268,7 +265,7 @@ very useful for development.
Default: Same as the Esmerald.
* **contact** - The contact of an admin. Used for OpenAPI.
-
+
Default: `{"name": "admin", "email": "admin@myapp.com"}`.
* **terms_of_service** - The terms of service of the application. Used for OpenAPI.
@@ -284,7 +281,7 @@ very useful for development.
Default: `None`.
* **secret_key** - The secret key used for internal encryption (for example, user passwords). We strongly advise to
-update this particular setting, mostly if the application uses the native [Tortoise](../databases/tortoise/motivation.md)
+update this particular setting, mostly if the application uses the native [Saffier](../databases/saffier/motivation.md)
support.
Default: `my secret`
@@ -363,9 +360,58 @@ application and not only for specific endpoints.
* **reload** - Boolean flag indicating if reload should happen (by default) on development and testing enviroment.
The default environment is `production`.
-
+
Default: `False`
+* **root_path_in_servers** - A Flag indicating if the root path should be included in the servers.
+
+ Default: `True`
+
+* **openapi_url** - URL of the openapi.json.
+
+ Default: `/openapi.json`
+
+ !!! Danger
+ Be careful when changing this one.
+
+* **redoc_url** - URL where the redoc should be served.
+
+ Default: `/docs/redoc`
+
+* **swagger_ui_oauth2_redirect_url** - URL to serve the UI oauth2 redirect.
+
+ Default: `/docs/oauth2-redirect`
+
+* **redoc_js_url** - URL of the redoc JS.
+
+ Default: `https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js`
+
+* **redoc_favicon_url** - URL for the redoc favicon.
+
+ Default: `https://esmerald.dev/statics/images/favicon.ico`
+
+* **swagger_ui_init_oauth** - Python dictionary format with OpenAPI specification for the swagger
+init oauth.
+
+ Default: `None`
+
+* **swagger_ui_parameters** - Python dictionary format with OpenAPI specification for the swagger ui
+parameters.
+
+ Default: `None`
+
+* **swagger_js_url** - URL of the swagger JS.
+
+ Default: `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js`
+
+* **swagger_css_url** - URL of the swagger CSS.
+
+ Default: `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css`
+
+* **swagger_favicon_url** - URL of the favicon for the swagger.
+
+ Default: `https://esmerald.dev/statics/images/favicon.ico`
+
* **password_hashers** - A list of [password hashers](../password-hashers.md) used for encryption of the passwords.
Default: `["esmerald.contrib.auth.hashers.PBKDF2PasswordHasher",
@@ -373,11 +419,11 @@ The default environment is `production`.
!!! warning
- The password hashers are linked to [Tortoise](../databases/tortoise/motivation.md) support and are used
+ The password hashers are linked to [Saffier](../databases/saffier/motivation.md) support and are used
with the models provided by default with Esmerald.
* **routes** - A list of routes to serve incoming HTTP and WebSocket requests.
-
+
Default: `[]`
!!! tip
diff --git a/docs/configurations/jwt.md b/docs/configurations/jwt.md
index d50dcc9c..42224822 100644
--- a/docs/configurations/jwt.md
+++ b/docs/configurations/jwt.md
@@ -24,8 +24,8 @@ To use the JWTConfig with a middleware.
```
!!! info
- The example uses a supported [JWTAuthMiddleware](../databases/tortoise/middleware.md#jwtauthmiddleware)
- from Esmerald with Tortoise ORM.
+ The example uses a supported [JWTAuthMiddleware](../databases/saffier/middleware.md#jwtauthmiddleware)
+ from Esmerald with Saffier ORM.
## Parameters
diff --git a/docs/configurations/openapi/apiview.md b/docs/configurations/openapi/apiview.md
deleted file mode 100644
index 0b76d877..00000000
--- a/docs/configurations/openapi/apiview.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# OpenAPIView
-
-This is a very special object that manages everything `OpenAPI` related documentation.
-
-In simple terms, the OpenAPIView simply creates the handlers for the `swagger` and `redoc` and registers those
-within your application routes.
-
-```python title='myapp/openapi/views.py'
-{!> ../docs_src/configurations/openapi/apiview.py!}
-```
-
-## Parameters
-
-There are a few internal parameteres being used by Esmerald and we **strongly recommend not to mess up with them
-unless you are confortable with everything** and only override the `path` parameter when needed.
-
-* **path** - The path prefix for the documentation.
-
- Default: `/docs`
-
-* **favicon** - The favicon used for the docs.
-
- Default: `https://esmerald.dymmond.com/statics/images/favicon.ico`
-
-## The documentation URLs
-
-Esmerald OpenAPI documentation by default will use `/docs` prefix to access the OpenAPI application documentation.
-
-* **Swagger** - `/docs/swagger`.
-* **Redoc** - `/docs/redoc`.
-
-### Overriding the default path
-
-Let's have another look at the example given above.
-
-```python title='myapp/openapi/views.py'
-{!> ../docs_src/configurations/openapi/apiview.py!}
-```
-
-Since the path prefix became `/another=url` you can now access the documentation via:
-
-* **Swagger** - `/another-url/swagger`.
-* **Redoc** - `/another-url/redoc`.
-
-!!! Tip
- The OpenAPI documentation works really well natively and we advise, once again, to be careful when overriding
- parameteres other than **`/path`**.
diff --git a/docs/configurations/openapi/config.md b/docs/configurations/openapi/config.md
index d45c5cb0..c6162dc0 100644
--- a/docs/configurations/openapi/config.md
+++ b/docs/configurations/openapi/config.md
@@ -2,33 +2,36 @@
OpenAPIConfig is a simple configuration with basic fields for the auto-genmerated documentation from Esmerald.
-There are two pieces for the documentation.
+Prior to version 2, there were two pieces for the documentation but now it is simplified with a simple
+one.
* [OpenAPIConfig](#openapiconfig)
-* [OpenAPIView](./apiview.md)
!!! Tip
- More information about OpenAPI
- here.
+ More information about
+ OpenAPI.
+
+You can create your own OpenAPIConfig and populate all the variables needed or you can simply
+override the settings attributes and allow Esmerald to handle the configuration on its own. It
+is up to you.
+
+!!! Warning
+ When passing OpenAPI attributes via instantiation, `Esmerald(docs_url='/docs/swagger',...)`,
+ those will always be used over the settings or custom configuration.
## OpenAPIConfig and application
-The `OpenAPIConfig` **needs** an [OpenAPIView] to make sure it serves the documentation properly.
+The `OpenAPIConfig` contains a bunch of simple fields that are needed to to serve the documentation
+and those can be easily overwritten.
-Currently, by default, it is using the Esmerald OpenAPIView pointing to:
+Currently, by default, the URL for the documentation are:
* **Swagger** - `/docs/swagger`.
-* **Redoc** - '/docs/redoc`.
+* **Redoc** - `/docs/redoc`.
## Parameters
-* **create_examples** - Generates doc examples.
-
- Default: `False`
-
-* **openapi_apiview** - The [OpenAPIView](./apiview.md) serving the docs.
-
- Default: `OpenAPIView`
+This are the parameters needed for the OpenAPIConfig if you want to create your own configuration.
* **title** - Title of the API documentation.
@@ -47,9 +50,7 @@ with OpenAPI or an instance of `openapi_schemas_pydantic.v3_1_0.contact.Contact`
Default: `Esmerald description`
-* **external_docs** - Links to external documentation. This is an OpenAPI schema external documentation, meaning,
-in a dictionary format compatible with OpenAPI or an instance of
-`openapi_schemas_pydantic.v3_1_0.external_documentation.ExternalDocumentation`.
+* **terms_of_service** - URL to a page that contains terms of service.
Default: `None`
@@ -59,48 +60,74 @@ in a dictionary format compatible with OpenAPI or an instance of
Default: `None`
-* **security** - API Security requirements information. This is an OpenAPI schema security, meaning,
-in a dictionary format compatible with OpenAPI or an instance of
-`openapi_schemas_pydantic.v3_1_0.security_requirement.SecurityRequirement`
-
- Default: `None`
-
-* **components** - A list of OpenAPI compatible `Server` information. OpenAPI specific dictionary or an instance of
-`openapi_schemas_pydantic.v3_10_0.components.Components`
+* **servers** - A python list with dictionary compatible with OpenAPI specification.
- Default: `None`
+ Default: `[{"url": "/"}]`
* **summary** - Simple summary text.
Default: `Esmerald summary`
+* **security** - API Security requirements information. This is an OpenAPI schema security, meaning,
+in a dictionary format compatible with OpenAPI or an instance of
+`openapi_schemas_pydantic.v3_1_0.security_requirement.SecurityScheme`
+
+ Default: `None`
+
* **tags** - A list of OpenAPI compatible `Tag` information. This is an OpenAPI schema tags, meaning,
in a dictionary format compatible with OpenAPI or an instance of `openapi_schemas_pydantic.v3_1_0.server.Server`.
Default: `None`
-* **terms_of_service** - URL to a page that contains terms of service.
+* **root_path_in_servers** - A Flag indicating if the root path should be included in the servers.
+
+ Default: `True`
+
+* **openapi_url** - URL of the openapi.json.
+
+ Default: `/openapi.json`
+
+ !!! Danger
+ Be careful when changing this one.
+
+* **redoc_url** - URL where the redoc should be served.
+
+ Default: `/docs/redoc`
+
+* **swagger_ui_oauth2_redirect_url** - URL to serve the UI oauth2 redirect.
+
+ Default: `/docs/oauth2-redirect`
+
+* **redoc_js_url** - URL of the redoc JS.
+
+ Default: `https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js`
+
+* **redoc_favicon_url** - URL for the redoc favicon.
+
+ Default: `https://esmerald.dev/statics/images/favicon.ico`
+
+* **swagger_ui_init_oauth** - Python dictionary format with OpenAPI specification for the swagger
+init oauth.
Default: `None`
-* **use_handler_docstrings** - Flag enabling to read the information from a [handler](../../routing/handlers.md)
-docstring if no description is provided.
+* **swagger_ui_parameters** - Python dictionary format with OpenAPI specification for the swagger ui
+parameters.
+
+ Default: `None`
- Default: `False`
+* **swagger_js_url** - URL of the swagger JS.
-* **webhooks** - A mapping of key to either an OpenAPI `PathItem` or an OpenAPI `Reference` instance. Both PathItem and
-Reference are in a dictionary format compatible with OpenAPI or an instance of
-`openapi_schemas_pydantic.v3_1_0.path_item.PathItem` or `openapi_schemas_pydantic.v3_1_0.reference.Reference`.
+ Default: `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js`
- Default: `False`
+* **swagger_css_url** - URL of the swagger CSS.
-* **root_schema_site** - Static schema generator to use for the "root" path of `/schema/`.
+ Default: `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css`
- Default: `redoc`
+* **swagger_favicon_url** - URL of the favicon for the swagger.
-* **enabled_endpoints** - A set of the enabled documentation sites and schema download endpoints.
+ Default: `https://esmerald.dev/statics/images/favicon.ico`
- Default: `{"redoc", "swagger", "elements", "openapi.json", "openapi.yaml"}`
### How to use or create an OpenAPIConfig
@@ -113,20 +140,12 @@ It is very simple actually.
This will create your own `OpenAPIConfig` and pass it to the Esmerald application but what about changing the current
default `/docs` path?
-You will need an [OpenAPIView](./apiview.md) to make it work.
-
Let's use a an example for clarification.
-```python title='myapp/openapi/views.py'
+```python
{!> ../docs_src/configurations/openapi/apiview.py!}
```
-Then you need to add the new APIView to your OpenAPIConfig.
-
-```python title='src/app.py'
-{!> ../docs_src/configurations/openapi/example2.py!}
-```
-
From now on the url to access the `swagger` and `redoc` will be:
* **Swagger** - `/another-url/swagger`.
@@ -141,11 +160,6 @@ settings.
{!> ../docs_src/configurations/openapi/settings.py!}
```
-!!! Warning
- We did import the `MyOpenAPIView` inside the property itself and the reason for it is to avoid import errors
- or any `mro` issues. Since the app once starts runs the settings once, there is no problem since it will not
- reconfigure on every single request.
-
Start the server with your custom settings.
```shell
diff --git a/docs/databases/saffier/models.md b/docs/databases/saffier/models.md
index 4ef41194..a6c2188a 100644
--- a/docs/databases/saffier/models.md
+++ b/docs/databases/saffier/models.md
@@ -13,7 +13,7 @@ initial configuration.
## User
-Extenting the existing `User` model is as simple as this:
+Extenting the existing `User` model is as simple as this:
```python hl_lines="17 32"
{!> ../docs_src/databases/saffier/models.py !}
@@ -145,12 +145,12 @@ You can always override the property `password_hashers` in your
[custom settings](../../application/settings.md#custom-settings) and use your own.
```python
-{!> ../docs_src/databases/tortoise/hashers.py !}
+{!> ../docs_src/databases/saffier/hashers.py !}
```
## Migrations
-You can use any migration tool as you see fit. It is recommended
+You can use any migration tool as you see fit. It is recommended
Alembic.
Saffier also provides some insights in
diff --git a/docs/dependencies.md b/docs/dependencies.md
index 597f2cb3..d9ebaaae 100644
--- a/docs/dependencies.md
+++ b/docs/dependencies.md
@@ -16,7 +16,7 @@ The dependencies are read from top-down in a python dictionary format, which mea
## How to use
-Assuming we have a `User` model using [Tortoise](./databases/tortoise/models.md).
+Assuming we have a `User` model using [Saffier](./databases/saffier/models.md).
```python hl_lines="14-15 19"
{!> ../docs_src/dependencies/precedent.py !}
@@ -47,4 +47,4 @@ and checks if the value is bigger or equal than 5 and that result `is_valid` is
{! ../docs_src/_shared/exceptions.md !}
-The same is applied also to [exception handlers](./exception-handlers.md).
\ No newline at end of file
+The same is applied also to [exception handlers](./exception-handlers.md).
diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md
index f58d848a..6dd2d99d 100644
--- a/docs/deployment/docker.md
+++ b/docs/deployment/docker.md
@@ -47,7 +47,7 @@ Let's use:
* All of configrations will be places in a folder called `/deployment`.
* The application will have a simple folder structure
-
+
```txt
.
├── app
@@ -213,7 +213,6 @@ and you can acess via:
* [http://127.0.0.1/swagger](http://127.0.0.1/docs/swagger)
* [http://127.0.0.1/redoc](http://127.0.0.1/docs/redoc)
-Or via your own custom [OpenAPIView](../configurations/openapi/apiview.md).
### Documentation in production
diff --git a/docs/index.md b/docs/index.md
index 42457d7e..3c4bb4d6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -413,13 +413,16 @@ INFO: Application startup complete.
Esmerald also comes with OpenAPI docs integrated. For those used to that, this is roughly the same and to make it
happen, there were inspirations that helped Esmerald getting there fast.
+Esmerald starts automatically the OpenAPI documentation by injecting the OpenAPIConfig default from
+the settings and makes Swagger and ReDoc available to you out of the box.
+
To access the OpenAPI, simply start your local development and access:
* **Swagger** - `/docs/swagger`.
* **Redoc** - `/docs/redoc`.
-There are more details about [how to configure the OpenAPIConfig](./configurations/openapi/config.md) and
-[create your own OpenAPIView](./configurations/openapi/apiview.md) within this documentation.
+There are more details about [how to configure the OpenAPIConfig](./configurations/openapi/config.md)
+within this documentation.
## Notes
diff --git a/docs/middleware/middleware.md b/docs/middleware/middleware.md
index 59d90383..2713175b 100644
--- a/docs/middleware/middleware.md
+++ b/docs/middleware/middleware.md
@@ -116,7 +116,7 @@ assigning the result object into a `AuthResult` and make it available on every r
3. Implement the `authenticate` and assign the `user` result to the `AuthResult`.
!!! Info
- We use [Tortoise-ORM](./../databases/tortoise/motivation.md) for this example because Esmerald supports tortoise
+ We use [Saffier](./../databases/saffier/motivation.md) for this example because Esmerald supports S
and contains functionalities linked with that support (like the User table) but **Esmerald**
**is not dependent of ANY specific ORM** which means that you are free to use whatever you prefer.
diff --git a/docs/password-hashers.md b/docs/password-hashers.md
index a372f3da..eea4c1bf 100644
--- a/docs/password-hashers.md
+++ b/docs/password-hashers.md
@@ -7,9 +7,9 @@ making a possible password even more secure.
## Esmerald and password hashing
-Esmerald supporting [Tortoise](./databases/tortoise/motivation.md) also means providing some of the features internally.
+Esmerald supporting [Saffier](./databases/saffier/motivation.md) also means providing some of the features internally.
-A lof of what is explained here is explained in more detail in the [tortoise orm support](./databases/tortoise/motivation.md).
+A lof of what is explained here is explained in more detail in the [Saffier orm support](./databases/saffier/motivation.md).
Esmerald already brings some pre-defined password hashers that are available in the
[Esmerald settings](./application/settings.md) and ready to be used.
@@ -31,7 +31,7 @@ You can always override the property `password_hashers` in your
[custom settings](./application/settings.md#custom-settings) and use your own.
```python
-{!> ../docs_src/databases/tortoise/hashers.py !}
+{!> ../docs_src/databases/saffier/hashers.py !}
```
## Current supported hashing
@@ -41,7 +41,7 @@ those. In fact, you can use your own completely from the scratch and use it with
!!! Tip
If you want to create your own password hashing, it is advisable to subclass the `BasePasswordHasher`.
-
+
```python
from esmerald.contrib.auth.hashers import BasePasswordHasher
```
diff --git a/docs/permissions.md b/docs/permissions.md
index 6fde2c89..aba87ea2 100644
--- a/docs/permissions.md
+++ b/docs/permissions.md
@@ -28,14 +28,14 @@ All the permission classes **must derive** from `BasePermission`.
## Esmerald and permissions
-Esmerald giving support to [Tortoise ORM](./databases/tortoise/motivation.md) also provides some default permissions
+Esmerald giving support to [Saffier ORM](./databases/saffier/motivation.md) also provides some default permissions
that can be linked to the models also provided by **Esmerald**.
### IsAdminUser and example of provided permissions
This is a simple permission that extends the `BaseAbstractUserPermission` and checks if a user is authenticated or not.
The functionality of verifying if a user might be or not authenticated was separated from the
-[Tortoise](./databases/tortoise/motivation.md) and instead you must implement the `is_user_authenticated()`
+[Saffier](./databases/saffier/motivation.md) and instead you must implement the `is_user_authenticated()`
function when inheriting from `BaseAbstractUserPermission` or `IsAdminUser`.
## Esmerald and provided permissions
@@ -63,12 +63,12 @@ To use the `IsAdminUser`, `IsAuthenticated` and `IsAuthenticatedOrReadOnly` is a
1. The main app `Esmerald` has an `AllowAny` permission for the top level
2. The `UserAPIView` object has a `IsUserAuthenticated` allowing only authenticated users to access any
of the endpoints under the class (endpoints under `/users`).
-3. The `/users/admin` has a permission `IsAdmin` allowing only admin users to access the specific endpoint
+3. The `/users/admin` has a permission `IsAdmin` allowing only admin users to access the specific endpoint
## Permissions summary
1. All permissions must inherit from `BasePermission`.
2. `BasePermission` has the `has_permission(request Request, apiview: "APIGateHandler").
3. The [handlers](./routing/handlers.md), [Gateway](./routing/routes.md#gateway),
-[WebSocketGateway](./routing/routes.md#websocketgateway), [Include](./routing/routes#include)
+[WebSocketGateway](./routing/routes.md#websocketgateway), [Include](./routing/routes#include)
and [Esmerald](./application/applications.md) can have as many permissions as you want.
diff --git a/docs/protocols.md b/docs/protocols.md
index 43155776..43173863 100644
--- a/docs/protocols.md
+++ b/docs/protocols.md
@@ -33,7 +33,7 @@ It is better to explain by using an example.
Let's imagine you need one handler that manages the creation of a user. Your application will have:
-* `Database connections`. Let's use the current supported [tortoise](./databases/tortoise/motivation.md).
+* `Database connections`. Let's use the current supported [Saffier](./databases/saffier/motivation.md).
* `Database models`. What is used to map python classes and database obbjects.
* `The handler`. What you will be calling.
@@ -42,7 +42,7 @@ Let's imagine you need one handler that manages the creation of a user. Your app
```
!!! Check
- Since we are using tortoise, all the database connections and configurations are handled by our settings.
+ Since we are using saffier, all the database connections and configurations are handled by our settings.
In this example, the handler manages to check if there is a user already with these details and creates if not but all
of this is managed in the handler itself. Sometimes is ok when is this simple but sometimes you might want to extend
@@ -85,7 +85,7 @@ object should be also doing.
In the example, simple CRUD was used but from there you can extend the functionality to, for instance, send emails,
call external services... With a big difference. From now one, all of your `User` operations will be managed by
-that same `DAO` and not by the view.
+that same `DAO` and not by the view.
Advantage? You have **one single source of truth** and not too many handlers
across the codebase doing similar `User` operations and increasing the probability of getting more errors and
@@ -104,7 +104,7 @@ This is a special protocol used to implement [interceptors](./interceptors.md) f
## Notes
Implementing the DAO/AsyncDAO protocol is as simple as subclassing it and implement the methods but this does not mean
-that you are only allowed to use those methods. No!
+that you are only allowed to use those methods. No!
In fact, that only means that when extending the DAO/AsyncDAO
you need **at least** to have those methods but you can have whatever you need for your business objects to operate.
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 6b68ae7e..b6ef473c 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,38 @@
# Release Notes
+## 2.0.0
+
+!!! Warning
+ When upgrading Esmerald to version 2, this also means the use of Pydantic 2.0 at its core as well as corresponsing technologies
+ already updated to version 2 of Pydantic (Saffier, Asyncz...).
+ If you still wish to continue to use Pydantic 1 with Esmerald, it is recommended to use Esmerald prior to version 2.0 which it will
+ be maintained for a shor period but we **strongly recommend to always use the latest version**.
+
+### Changed
+
+- **Major update of the core of Esmerald from Pydantic v1 to Pydantic v2.**
+- Changed deprecated functions such as `validator` and `root_validator` to `field_validator` and `model_validator`.
+- Transformers no longer support custom fields. Pydantic natively handles that.
+- EsmeraldSignature updated for the new version of the FieldInfo.
+- `params` reflect the new Pydantic FieldInfo.
+- Deprecated OpenAPIView in favour of the new OpenAPI documentation generator.
+- Changed OpenAPI config to reflect the new generation of OpenAPI documentation.
+- Internal data field is now returning Body type parameter making it easier to integrate with Pydantic 2.0.
+- General codebase cleanup.
+- Removed old OpenAPI document generator in favour to the newest, fastest, simplest and more effective approach in v2.
+- Remove the support of pydantic 1.0. Esmerald 2+ will only support pydantic 2+.
+
+### Added
+
+- OpenAPI support for OAuth2.
+
+### Fixed
+
+- FileResponse `stat_result` and Stream `iterator` typing.
+- Fix typing across the whole codebase.
+- Transformers are now generating Param fields directly.
+- Updated __fields__ in favour of the new pydantic model_fields approach.
+
## 1.3.0
### Changed
@@ -470,7 +503,7 @@ and can be used for testing purposes or to clear a session.
### Changed
-- Removed [Tortoise ORM](./databases/tortoise/motivation.md#how-to-use) dependency from the main package.
+- Removed Tortoise ORM dependency from the main package.
- Removed `asyncpg` from the main package as dependency.
## 0.2.5
@@ -556,7 +589,7 @@ version to the most advanced.
- **Exception Handlers**: Apply exception handlers on any desired level.
- **Permissions**: Apply specific rules and permissions on each API.
- **DAO and AsyncDAO**: Avoid database calls directly from the APIs. Use business objects instead.
-- **Tortoise ORM**: Native support for [Tortoise ORM](./databases/tortoise/motivation.md).
+- **Tortoise ORM**: Native support for Tortoise ORM.
- **APIView**: Class Based endpoints for your beloved OOP design.
- **JSON serialization/deserialization**: Both UJSON and ORJON support.
- **Lifespan**: Support for the newly lifespan and on_start/on_shutdown events.
diff --git a/docs/responses.md b/docs/responses.md
index 31d8c17c..f8f521e3 100644
--- a/docs/responses.md
+++ b/docs/responses.md
@@ -227,7 +227,7 @@ This is a special attribute that is used for OpenAPI spec purposes and can be cr
from typing import Union
from esmerald import post
-from esmerald.openapi.datastructures import ResponseSpecification
+from esmerald.openapi.datastructures import OpenAPIResponse
from pydantic import BaseModel
@@ -236,7 +236,7 @@ class ItemOut(BaseModel):
description: str
-@post(path='/create', summary="Creates an item", responses={200: ResponseSpecification(model=TaskIn, description=...)})
+@post(path='/create', summary="Creates an item", responses={200: OpenAPIResponse(model=TaskIn, description=...)})
async def create() -> Union[None, ItemOut]:
...
```
diff --git a/docs_src/configurations/openapi/apiview.py b/docs_src/configurations/openapi/apiview.py
index 71a9afe7..613d898d 100644
--- a/docs_src/configurations/openapi/apiview.py
+++ b/docs_src/configurations/openapi/apiview.py
@@ -1,5 +1,7 @@
-from esmerald.openapi.apiview import OpenAPIView
+from esmerald import Esmerald
-
-class MyOpenAPIView(OpenAPIView):
- path = "/another-url"
+app = Esmerald(
+ routes=[...],
+ docs_url="/another-url/swagger",
+ redoc_url="/another-url/redoc",
+)
diff --git a/docs_src/configurations/openapi/example1.py b/docs_src/configurations/openapi/example1.py
index 3327ea6a..e1ebe9bf 100644
--- a/docs_src/configurations/openapi/example1.py
+++ b/docs_src/configurations/openapi/example1.py
@@ -1,11 +1,11 @@
from esmerald import Esmerald, OpenAPIConfig
+from esmerald.openapi.models import Contact
+openapi_config = OpenAPIConfig(
+ title="My Application",
+ docs_url="/mydocs/swagger",
+ redoc_url="/mydocs/redoc",
+ contact=Contact(name="User", email="email@example.com"),
+)
-class MyOpenAPIConfig(OpenAPIConfig):
- # Do you want to generate examples?
- create_examples: bool = True
- title: str = ...
- version: str = ...
-
-
-app = Esmerald(routes=[...], openapi_config=MyOpenAPIConfig)
+app = Esmerald(routes=[...], openapi_config=openapi_config)
diff --git a/docs_src/configurations/openapi/example2.py b/docs_src/configurations/openapi/example2.py
deleted file mode 100644
index bc0c9ad1..00000000
--- a/docs_src/configurations/openapi/example2.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from myapp.openapi.views import MyOpenAPIView
-
-from esmerald import Esmerald, OpenAPIConfig
-
-
-class MyOpenAPIConfig(OpenAPIConfig):
- # Do you want to generate examples?
- openapi_apiview = MyOpenAPIView
- create_examples: bool = True
- title: str = ...
- version: str = ...
-
-
-app = Esmerald(routes=[...], openapi_config=MyOpenAPIConfig)
diff --git a/docs_src/configurations/openapi/settings.py b/docs_src/configurations/openapi/settings.py
index 10a48e78..4b7ff222 100644
--- a/docs_src/configurations/openapi/settings.py
+++ b/docs_src/configurations/openapi/settings.py
@@ -7,10 +7,7 @@ def openapi_config(self) -> OpenAPIConfig:
"""
Override the default openapi_config from Esmerald.
"""
- from myapp.openapi.views import MyOpenAPIView
-
return OpenAPIConfig(
- openapi_apiview=MyOpenAPIView,
title=self.title,
version=self.version,
contact=self.contact,
@@ -21,4 +18,17 @@ def openapi_config(self) -> OpenAPIConfig:
summary=self.summary,
security=self.security,
tags=self.tags,
+ docs_url=self.docs_url,
+ redoc_url=self.redoc_url,
+ swagger_ui_oauth2_redirect_url=self.swagger_ui_oauth2_redirect_url,
+ redoc_js_url=self.redoc_js_url,
+ redoc_favicon_url=self.redoc_favicon_url,
+ swagger_ui_init_oauth=self.swagger_ui_init_oauth,
+ swagger_ui_parameters=self.swagger_ui_parameters,
+ swagger_js_url=self.swagger_js_url,
+ swagger_css_url=self.swagger_css_url,
+ swagger_favicon_url=self.swagger_favicon_url,
+ root_path_in_servers=self.root_path_in_servers,
+ openapi_version=self.openapi_version,
+ openapi_url=self.openapi_url,
)
diff --git a/esmerald/__init__.py b/esmerald/__init__.py
index 7d3c6bd3..c4c3e4bf 100644
--- a/esmerald/__init__.py
+++ b/esmerald/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "1.3.0"
+__version__ = "2.0.0"
from starlette import status
diff --git a/esmerald/applications.py b/esmerald/applications.py
index a43b7d6e..4f7cedd6 100644
--- a/esmerald/applications.py
+++ b/esmerald/applications.py
@@ -13,7 +13,7 @@
cast,
)
-from openapi_schemas_pydantic.v3_1_0 import Contact, License, SecurityRequirement, Server, Tag
+from openapi_schemas_pydantic.v3_1_0 import Contact, License, SecurityScheme, Tag
from openapi_schemas_pydantic.v3_1_0.open_api import OpenAPI
from pydantic import AnyUrl
from starlette.applications import Starlette
@@ -130,8 +130,8 @@ def __init__(
contact: Optional[Contact] = None,
terms_of_service: Optional[AnyUrl] = None,
license: Optional[License] = None,
- security: Optional[List[SecurityRequirement]] = None,
- servers: Optional[List[Server]] = None,
+ security: Optional[List[SecurityScheme]] = None,
+ servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
secret_key: Optional[str] = None,
allowed_hosts: Optional[List[str]] = None,
allow_origins: Optional[List[str]] = None,
@@ -140,6 +140,7 @@ def __init__(
dependencies: Optional["Dependencies"] = None,
csrf_config: Optional["CSRFConfig"] = None,
openapi_config: Optional["OpenAPIConfig"] = None,
+ openapi_version: Optional[str] = None,
cors_config: Optional["CORSConfig"] = None,
static_files_config: Optional["StaticFilesConfig"] = None,
template_config: Optional["TemplateConfig"] = None,
@@ -166,6 +167,18 @@ def __init__(
redirect_slashes: Optional[bool] = None,
pluggables: Optional[Dict[str, Pluggable]] = None,
parent: Optional[Union["ParentType", "Esmerald", "ChildEsmerald"]] = None,
+ root_path_in_servers: bool = None,
+ openapi_url: Optional[str] = None,
+ docs_url: Optional[str] = None,
+ redoc_url: Optional[str] = None,
+ swagger_ui_oauth2_redirect_url: Optional[str] = None,
+ redoc_js_url: Optional[str] = None,
+ redoc_favicon_url: Optional[str] = None,
+ swagger_ui_init_oauth: Optional[Dict[str, Any]] = None,
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
+ swagger_js_url: Optional[str] = None,
+ swagger_css_url: Optional[str] = None,
+ swagger_favicon_url: Optional[str] = None,
) -> None:
self.settings_config = None
@@ -196,6 +209,7 @@ def __init__(
else self.get_settings_value(self.settings_config, esmerald_settings, "debug")
)
self.debug = self._debug
+
self.title = title or self.get_settings_value(
self.settings_config, esmerald_settings, "title"
)
@@ -208,6 +222,9 @@ def __init__(
self.version = version or self.get_settings_value(
self.settings_config, esmerald_settings, "version"
)
+ self.openapi_version = openapi_version or self.get_settings_value(
+ self.settings_config, esmerald_settings, "openapi_version"
+ )
self.summary = summary or self.get_settings_value(
self.settings_config, esmerald_settings, "summary"
)
@@ -352,6 +369,95 @@ def __init__(
else self.get_settings_value(self.settings_config, esmerald_settings, "pluggables")
)
+ # OpenAPI Related
+ self.root_path_in_servers = (
+ root_path_in_servers
+ if root_path_in_servers is not None
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "root_path_in_servers"
+ )
+ )
+ if not self.include_in_schema or not self.enable_openapi:
+ self.root_path_in_servers = False
+
+ self.openapi_url = (
+ openapi_url
+ if openapi_url
+ else self.get_settings_value(self.settings_config, esmerald_settings, "openapi_url")
+ )
+
+ self.docs_url = (
+ docs_url
+ if docs_url
+ else self.get_settings_value(self.settings_config, esmerald_settings, "docs_url")
+ )
+
+ self.redoc_url = (
+ redoc_url
+ if redoc_url
+ else self.get_settings_value(self.settings_config, esmerald_settings, "redoc_url")
+ )
+
+ self.swagger_ui_oauth2_redirect_url = (
+ swagger_ui_oauth2_redirect_url
+ if swagger_ui_oauth2_redirect_url
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "swagger_ui_oauth2_redirect_url"
+ )
+ )
+
+ self.redoc_js_url = (
+ redoc_js_url
+ if redoc_js_url
+ else self.get_settings_value(self.settings_config, esmerald_settings, "redoc_js_url")
+ )
+
+ self.redoc_favicon_url = (
+ redoc_favicon_url
+ if redoc_favicon_url
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "redoc_favicon_url"
+ )
+ )
+
+ self.swagger_ui_init_oauth = (
+ swagger_ui_init_oauth
+ if swagger_ui_init_oauth
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "swagger_ui_init_oauth"
+ )
+ )
+
+ self.swagger_ui_parameters = (
+ swagger_ui_parameters
+ if swagger_ui_parameters
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "swagger_ui_parameters"
+ )
+ )
+
+ self.swagger_js_url = (
+ swagger_js_url
+ if swagger_js_url
+ else self.get_settings_value(self.settings_config, esmerald_settings, "swagger_js_url")
+ )
+
+ self.swagger_css_url = (
+ swagger_css_url
+ if swagger_css_url
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "swagger_css_url"
+ )
+ )
+
+ self.swagger_favicon_url = (
+ swagger_favicon_url
+ if swagger_favicon_url
+ else self.get_settings_value(
+ self.settings_config, esmerald_settings, "swagger_favicon_url"
+ )
+ )
+
self.openapi_schema: Optional["OpenAPI"] = None
self.state = State()
self.async_exit_config = esmerald_settings.async_exit_config
@@ -423,10 +529,58 @@ def get_settings_value(
return setting_value
def activate_openapi(self) -> None:
- if self.openapi_config and self.enable_openapi:
- self.openapi_schema = self.openapi_config.create_openapi_schema_model(self)
- gateway = gateways.Gateway(handler=self.openapi_config.openapi_apiview) # type: ignore
- self.add_apiview(value=gateway)
+ if self.enable_openapi:
+ if self.title or not self.openapi_config.title:
+ self.openapi_config.title = self.title
+ if self.version or not self.openapi_config.version:
+ self.openapi_config.version = self.version
+ if self.openapi_version or not self.openapi_config.openapi_version:
+ self.openapi_config.openapi_version = self.openapi_version
+ if self.summary or not self.openapi_config.summary:
+ self.openapi_config.summary = self.summary
+ if self.description or not self.openapi_config.description:
+ self.openapi_config.description = self.description
+ if self.tags or not self.openapi_config.tags:
+ self.openapi_config.tags = self.tags
+ if self.servers or not self.openapi_config.servers:
+ self.openapi_config.servers = self.servers
+ if self.terms_of_service or not self.openapi_config.terms_of_service:
+ self.openapi_config.terms_of_service = self.terms_of_service
+ if self.contact or not self.openapi_config.contact:
+ self.openapi_config.contact = self.contact
+ if self.license or not self.openapi_config.license:
+ self.openapi_config.license = self.license
+ if self.root_path_in_servers or not self.openapi_config.root_path_in_servers:
+ self.openapi_config.root_path_in_servers = self.root_path_in_servers
+ if self.docs_url or not self.openapi_config.docs_url:
+ self.openapi_config.docs_url = self.docs_url
+ if self.redoc_url or not self.openapi_config.redoc_url:
+ self.openapi_config.redoc_url = self.redoc_url
+ if (
+ self.swagger_ui_oauth2_redirect_url
+ or not self.openapi_config.swagger_ui_oauth2_redirect_url
+ ):
+ self.openapi_config.swagger_ui_oauth2_redirect_url = (
+ self.swagger_ui_oauth2_redirect_url
+ )
+ if self.redoc_js_url or not self.openapi_config.redoc_js_url:
+ self.openapi_config.redoc_js_url = self.redoc_js_url
+ if self.redoc_favicon_url or not self.openapi_config.redoc_favicon_url:
+ self.openapi_config.redoc_favicon_url = self.redoc_favicon_url
+ if self.swagger_ui_init_oauth or not self.openapi_config.swagger_ui_init_oauth:
+ self.openapi_config.swagger_ui_init_oauth = self.swagger_ui_init_oauth
+ if self.swagger_ui_parameters or not self.openapi_config.swagger_ui_parameters:
+ self.openapi_config.swagger_ui_parameters = self.swagger_ui_parameters
+ if self.swagger_js_url or not self.openapi_config.swagger_js_url:
+ self.openapi_config.swagger_js_url = self.swagger_js_url
+ if self.swagger_css_url or not self.openapi_config.swagger_css_url:
+ self.openapi_config.swagger_css_url = self.swagger_css_url
+ if self.swagger_favicon_url or not self.openapi_config.swagger_favicon_url:
+ self.openapi_config.swagger_favicon_url = self.swagger_favicon_url
+ if self.openapi_url or not self.openapi_config.openapi_url:
+ self.openapi_config.openapi_url = self.openapi_url
+
+ self.openapi_config.enable(self)
def get_template_engine(
self, template_config: "TemplateConfig"
@@ -437,7 +591,7 @@ def get_template_engine(
if not template_config:
return None
- engine = template_config.engine(template_config.directory)
+ engine: "TemplateEngineProtocol" = template_config.engine(template_config.directory)
return engine
def add_apiview(
@@ -461,6 +615,7 @@ def add_route(
middleware: Optional[List["Middleware"]] = None,
name: Optional[str] = None,
include_in_schema: bool = True,
+ activate_openapi: bool = True,
) -> None:
"""
Adds a route into the router.
@@ -478,7 +633,8 @@ def add_route(
include_in_schema=include_in_schema,
)
- self.activate_openapi()
+ if activate_openapi:
+ self.activate_openapi()
def add_websocket_route(
self,
@@ -521,7 +677,7 @@ def add_child_esmerald(
permissions: Optional[List["Permission"]] = None,
include_in_schema: Optional[bool] = True,
deprecated: Optional[bool] = None,
- security: Optional[List["SecurityRequirement"]] = None,
+ security: Optional[List["SecurityScheme"]] = None,
) -> None:
"""
Adds a child esmerald into the application routers.
diff --git a/esmerald/conf/__init__.py b/esmerald/conf/__init__.py
index 352633b8..60ddcee1 100644
--- a/esmerald/conf/__init__.py
+++ b/esmerald/conf/__init__.py
@@ -28,7 +28,7 @@ def _setup(self, name: Optional[str] = None) -> None:
settings: Any = import_string(settings_module)
- for setting, _ in settings().dict().items():
+ for setting, _ in settings().model_dump().items():
assert setting.islower(), "%s should be in lowercase." % setting
self._wrapped = settings()
diff --git a/esmerald/conf/global_settings.py b/esmerald/conf/global_settings.py
index 2cc539ce..e92f9cc4 100644
--- a/esmerald/conf/global_settings.py
+++ b/esmerald/conf/global_settings.py
@@ -1,8 +1,9 @@
from functools import cached_property
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
-from openapi_schemas_pydantic.v3_1_0 import Contact, License, SecurityRequirement, Server, Tag
-from pydantic import AnyUrl, BaseConfig, BaseSettings
+from openapi_schemas_pydantic.v3_1_0 import Contact, License, SecurityScheme, Tag
+from pydantic import AnyUrl
+from pydantic_settings import BaseSettings, SettingsConfigDict
from starlette.types import Lifespan
from esmerald import __version__
@@ -32,16 +33,17 @@ class EsmeraldAPISettings(BaseSettings):
debug: bool = False
environment: Optional[str] = EnvironmentType.PRODUCTION
app_name: str = "Esmerald"
- title: str = "My awesome Esmerald application"
+ title: str = "Esmerald"
description: str = "Highly scalable, performant, easy to learn and for every application."
contact: Optional[Contact] = Contact(name="admin", email="admin@myapp.com")
summary: str = "Esmerald application"
terms_of_service: Optional[AnyUrl] = None
license: Optional[License] = None
- security: Optional[List[SecurityRequirement]] = None
- servers: List[Server] = [Server(url="/")]
+ security: Optional[List[SecurityScheme]] = None
+ servers: List[Dict[str, Union[str, Any]]] = [{"url": "/"}]
secret_key: str = "my secret"
version: str = __version__
+ openapi_version: str = "3.1.0"
allowed_hosts: Optional[List[str]] = ["*"]
allow_origins: Optional[List[str]] = None
response_class: Optional[ResponseType] = None
@@ -56,10 +58,21 @@ class EsmeraldAPISettings(BaseSettings):
enable_scheduler: bool = False
enable_openapi: bool = True
redirect_slashes: bool = True
-
- class Config(BaseConfig):
- extra = "allow" # type: ignore
- keep_untouched = (cached_property,)
+ root_path_in_servers: bool = True
+ openapi_url: Optional[str] = "/openapi.json"
+ docs_url: Optional[str] = "/docs/swagger"
+ redoc_url: Optional[str] = "/docs/redoc"
+ swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect"
+ redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
+ redoc_favicon_url: str = "https://esmerald.dev/statics/images/favicon.ico"
+ swagger_ui_init_oauth: Optional[Dict[str, Any]] = None
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None
+ swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"
+ swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css"
+ swagger_favicon_url: str = "https://esmerald.dev/statics/images/favicon.ico"
+
+ # Model configuration
+ model_config = SettingsConfigDict(extra="allow", ignored_types=(cached_property,))
@property
def reload(self) -> bool:
@@ -236,10 +249,7 @@ class MySettings(EsmeraldAPISettings):
def openapi_config(self) -> OpenAPIConfig:
...
"""
- from esmerald.openapi.apiview import OpenAPIView
-
return OpenAPIConfig(
- openapi_apiview=OpenAPIView,
title=self.title,
version=self.version,
contact=self.contact,
@@ -250,6 +260,19 @@ def openapi_config(self) -> OpenAPIConfig:
summary=self.summary,
security=self.security,
tags=self.tags,
+ docs_url=self.docs_url,
+ redoc_url=self.redoc_url,
+ swagger_ui_oauth2_redirect_url=self.swagger_ui_oauth2_redirect_url,
+ redoc_js_url=self.redoc_js_url,
+ redoc_favicon_url=self.redoc_favicon_url,
+ swagger_ui_init_oauth=self.swagger_ui_init_oauth,
+ swagger_ui_parameters=self.swagger_ui_parameters,
+ swagger_js_url=self.swagger_js_url,
+ swagger_css_url=self.swagger_css_url,
+ swagger_favicon_url=self.swagger_favicon_url,
+ root_path_in_servers=self.root_path_in_servers,
+ openapi_version=self.openapi_version,
+ openapi_url=self.openapi_url,
)
@property
diff --git a/esmerald/config/openapi.py b/esmerald/config/openapi.py
index 1e81a01a..3d9ca871 100644
--- a/esmerald/config/openapi.py
+++ b/esmerald/config/openapi.py
@@ -1,173 +1,137 @@
-from functools import partial
-from typing import TYPE_CHECKING, Dict, List, Optional, Set, Type, Union
-
-from openapi_schemas_pydantic import construct_open_api_with_schema_class
-from openapi_schemas_pydantic.v3_1_0 import (
- Components,
- Contact,
- ExternalDocumentation,
- Info,
- License,
- OpenAPI,
- PathItem,
- Reference,
- SecurityRequirement,
- Server,
- Tag,
-)
-from pydantic import AnyUrl, BaseModel
-from typing_extensions import Literal
+from typing import Any, Dict, List, Optional, Union
-from esmerald.enums import HttpMethod
-from esmerald.openapi.path_item import create_path_item
-from esmerald.routing.gateways import Gateway, WebSocketGateway
-from esmerald.routing.router import Include
-from esmerald.utils.helpers import is_class_and_subclass
-from esmerald.utils.url import clean_path
+from openapi_schemas_pydantic.v3_1_0.security_scheme import SecurityScheme
+from pydantic import AnyUrl, BaseModel
-if TYPE_CHECKING:
- from esmerald.applications import Esmerald
- from esmerald.openapi.apiview import OpenAPIView
+from esmerald.openapi.docs import (
+ get_redoc_html,
+ get_swagger_ui_html,
+ get_swagger_ui_oauth2_redirect_html,
+)
+from esmerald.openapi.models import Contact, License, Tag
+from esmerald.openapi.openapi import get_openapi
+from esmerald.requests import Request
+from esmerald.responses import HTMLResponse, JSONResponse
+from esmerald.routing.handlers import get
class OpenAPIConfig(BaseModel):
- create_examples: bool = False
- openapi_apiview: Type["OpenAPIView"]
- title: str
- version: str
- contact: Optional[Contact] = None
+ title: Optional[str] = None
+ version: Optional[str] = None
+ summary: Optional[str] = None
description: Optional[str] = None
- external_docs: Optional[ExternalDocumentation] = None
+ contact: Optional[Contact] = None
+ terms_of_service: Optional[AnyUrl] = None
license: Optional[License] = None
- security: Optional[List[SecurityRequirement]] = None
- components: Optional[Union[Components, List[Components]]] = None
- servers: List[Server] = [Server(url="/")]
- summary: Optional[str] = None
+ security: Optional[List[SecurityScheme]] = None
+ servers: Optional[List[Dict[str, Union[str, Any]]]] = None
tags: Optional[List[Tag]] = None
- terms_of_service: Optional[AnyUrl] = None
- use_handler_docstrings: bool = False
- webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None
- root_schema_site: Literal["redoc", "swagger", "elements"] = "redoc"
- enabled_endpoints: Set[str] = {
- "redoc",
- "swagger",
- "elements",
- "openapi.json",
- "openapi.yaml",
- }
-
- def to_openapi_schema(self) -> "OpenAPI":
- if isinstance(self.components, list):
- merged_components = Components()
- for components in self.components:
- for key in components.__fields__.keys():
- value = getattr(components, key, None)
- if value:
- merged_value_dict = getattr(merged_components, key, {}) or {}
- merged_value_dict.update(value)
- setattr(merged_components, key, merged_value_dict)
- self.components = merged_components
-
- return OpenAPI(
- externalDocs=self.external_docs,
- security=self.security,
- components=self.components,
- servers=self.servers,
+ openapi_version: Optional[str] = None
+ openapi_url: Optional[str] = None
+ root_path_in_servers: bool = True
+ docs_url: Optional[str] = None
+ redoc_url: Optional[str] = None
+ swagger_ui_oauth2_redirect_url: Optional[str] = None
+ redoc_js_url: str = None
+ redoc_favicon_url: str = None
+ swagger_ui_init_oauth: Optional[Dict[str, Any]] = None
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None
+ swagger_js_url: Optional[str] = None
+ swagger_css_url: Optional[str] = None
+ swagger_favicon_url: Optional[str] = None
+
+ def openapi(self, app: Any) -> Dict[str, Any]:
+ """Loads the OpenAPI routing schema"""
+ openapi_schema = get_openapi(
+ title=self.title,
+ version=self.version,
+ openapi_version=self.openapi_version,
+ summary=self.summary,
+ description=self.description,
+ routes=app.routes,
tags=self.tags,
- webhooks=self.webhooks,
- info=Info(
- title=self.title,
- version=self.version,
- description=self.description,
- contact=self.contact,
- license=self.license,
- summary=self.summary,
- termsOfService=self.terms_of_service,
- ),
+ servers=self.servers,
+ terms_of_service=self.terms_of_service,
+ contact=self.contact,
+ license=self.license,
)
-
- def get_http_verb(self, path_item: PathItem) -> str:
- if getattr(path_item, "get", None):
- return HttpMethod.GET.value.lower()
- elif getattr(path_item, "post", None):
- return HttpMethod.POST.value.lower()
- elif getattr(path_item, "put", None):
- return HttpMethod.PUT.value.lower()
- elif getattr(path_item, "patch", None):
- return HttpMethod.PATCH.value.lower()
- elif getattr(path_item, "delete", None):
- return HttpMethod.DELETE.value.lower()
- elif getattr(path_item, "header", None):
- return HttpMethod.HEAD.value.lower()
-
- return HttpMethod.GET.value.lower()
-
- def create_openapi_schema_model(self, app: "Esmerald") -> "OpenAPI":
- from esmerald.applications import ChildEsmerald, Esmerald
-
- schema = self.to_openapi_schema()
- schema.paths = {}
-
- def parse_route(app, prefix=""): # type: ignore
- if getattr(app, "routes", None) is None:
- return
-
- # Making sure that ChildEsmerald or esmerald
- if hasattr(app, "app"):
- if (
- isinstance(app.app, (Esmerald, ChildEsmerald))
- or (
- is_class_and_subclass(app.app, Esmerald)
- or is_class_and_subclass(app.app, ChildEsmerald)
- )
- ) and not getattr(app.app, "enable_openapi", False):
- return
-
- for route in app.routes:
- if isinstance(route, Include) and not route.include_in_schema:
- continue
-
- if isinstance(route, WebSocketGateway):
- continue
-
- if isinstance(route, Gateway):
- if route.include_in_schema is False:
- continue
-
- if (
- isinstance(route, Gateway)
- and any(
- handler.include_in_schema
- for handler, _ in route.handler.route_map.values()
- )
- and (route.path_format or "/") not in schema.paths
- ):
- path = clean_path(prefix + route.path)
- path_item = create_path_item(
- route=route.handler, # type: ignore
- create_examples=self.create_examples,
- use_handler_docstrings=self.use_handler_docstrings,
- )
- verb = self.get_http_verb(path_item)
- if path not in schema.paths:
- schema.paths[path] = {} # type: ignore
- if verb not in schema.paths[path]: # type: ignore
- schema.paths[path][verb] = {} # type: ignore
- schema.paths[path][verb] = getattr(path_item, verb, None) # type: ignore
- continue
-
- route_app = getattr(route, "app", None)
- if not route_app:
- continue
-
- if isinstance(route_app, partial):
- try:
- route_app = route_app.__wrapped__
- except AttributeError:
- pass
-
- path = clean_path(prefix + route.path)
- parse_route(route, prefix=f"{path}") # type: ignore
-
- parse_route(app) # type: ignore
- return construct_open_api_with_schema_class(schema)
+ app.openapi_schema = openapi_schema
+ return openapi_schema
+
+ def enable(self, app: Any) -> None:
+ """Enables the OpenAPI documentation"""
+ if self.openapi_url:
+ urls = {server.get("url") for server in self.servers}
+ server_urls = set(urls)
+
+ @get(path=self.openapi_url)
+ async def _openapi(request: Request) -> JSONResponse:
+ root_path = request.scope.get("root_path", "").rstrip("/")
+ if root_path not in server_urls:
+ if root_path and self.root_path_in_servers:
+ self.servers.insert(0, {"url": root_path})
+ server_urls.add(root_path)
+ return JSONResponse(self.openapi(app))
+
+ app.add_route(
+ path="/", handler=_openapi, include_in_schema=False, activate_openapi=False
+ )
+
+ if self.openapi_url and self.docs_url:
+
+ @get(path=self.docs_url)
+ async def swagger_ui_html(request: Request) -> HTMLResponse:
+ root_path = request.scope.get("root_path", "").rstrip("/")
+ openapi_url = root_path + self.openapi_url
+ oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
+ if oauth2_redirect_url:
+ oauth2_redirect_url = root_path + oauth2_redirect_url
+ return get_swagger_ui_html(
+ openapi_url=openapi_url,
+ title=self.title + " - Swagger UI",
+ oauth2_redirect_url=oauth2_redirect_url,
+ init_oauth=self.swagger_ui_init_oauth,
+ swagger_ui_parameters=self.swagger_ui_parameters,
+ swagger_js_url=self.swagger_js_url,
+ swagger_favicon_url=self.swagger_favicon_url,
+ swagger_css_url=self.swagger_css_url,
+ )
+
+ app.add_route(
+ path="/",
+ handler=swagger_ui_html,
+ include_in_schema=False,
+ activate_openapi=False,
+ )
+
+ if self.swagger_ui_oauth2_redirect_url:
+
+ @get(self.swagger_ui_oauth2_redirect_url)
+ async def swagger_ui_redirect(request: Request) -> HTMLResponse:
+ return get_swagger_ui_oauth2_redirect_html()
+
+ app.add_route(
+ path="/",
+ handler=swagger_ui_redirect,
+ include_in_schema=False,
+ activate_openapi=False,
+ )
+
+ if self.openapi_url and self.redoc_url:
+
+ @get(self.redoc_url)
+ async def redoc_html(request: Request) -> HTMLResponse:
+ root_path = request.scope.get("root_path", "").rstrip("/")
+ openapi_url = root_path + self.openapi_url
+ return get_redoc_html(
+ openapi_url=openapi_url,
+ title=self.title + " - ReDoc",
+ redoc_js_url=self.redoc_js_url,
+ redoc_favicon_url=self.redoc_favicon_url,
+ )
+
+ app.add_route(
+ path="/", handler=redoc_html, include_in_schema=False, activate_openapi=False
+ )
+
+ app.router.activate()
diff --git a/esmerald/config/session.py b/esmerald/config/session.py
index 96527161..b8cd0867 100644
--- a/esmerald/config/session.py
+++ b/esmerald/config/session.py
@@ -1,6 +1,6 @@
from typing import Union
-from pydantic import BaseConfig, BaseModel, constr, validator
+from pydantic import BaseModel, ConfigDict, constr, field_validator
from typing_extensions import Literal
from esmerald.datastructures import Secret
@@ -9,8 +9,7 @@
class SessionConfig(BaseModel):
- class Config(BaseConfig):
- arbitrary_types_allowed = True
+ model_config = ConfigDict(arbitrary_types_allowed=True)
secret_key: Union[str, Secret]
path: str = "/"
@@ -19,8 +18,8 @@ class Config(BaseConfig):
https_only: bool = False
same_site: Literal["lax", "strict", "none"] = "lax"
- @validator("secret_key", always=True)
- def validate_secret(cls, value: Secret) -> Secret: # pylint: disable=no-self-argument
+ @field_validator("secret_key")
+ def validate_secret(cls, value: Secret) -> Secret:
if len(value) not in [16, 24, 32]:
raise ValueError("secret length must be 16 (128 bit), 24 (192 bit) or 32 (256 bit)")
return value
diff --git a/esmerald/config/static_files.py b/esmerald/config/static_files.py
index 76898c06..49b13418 100644
--- a/esmerald/config/static_files.py
+++ b/esmerald/config/static_files.py
@@ -1,6 +1,7 @@
-from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
-from pydantic import BaseModel, DirectoryPath, constr, validator
+from pydantic import BaseModel, DirectoryPath, constr, field_validator
from starlette.staticfiles import StaticFiles
from esmerald.utils.url import clean_path
@@ -11,13 +12,13 @@
class StaticFilesConfig(BaseModel):
path: constr(min_length=1) # type: ignore
- directory: Optional[DirectoryPath] = None
+ directory: Optional[Union[DirectoryPath, str, Path, Any]] = None
html: bool = False
packages: Optional[List[Union[str, Tuple[str, str]]]] = None
check_dir: bool = True
- @validator("path")
- def validate_path(cls, value: str) -> str: # pylint: disable=no-self-argument
+ @field_validator("path")
+ def validate_path(cls, value: str) -> str:
if "{" in value:
raise ValueError("path parameters are not supported for static files")
return clean_path(value)
diff --git a/esmerald/config/template.py b/esmerald/config/template.py
index 46fd8d75..75c90ea9 100644
--- a/esmerald/config/template.py
+++ b/esmerald/config/template.py
@@ -1,14 +1,13 @@
from typing import List, Type, Union
-from pydantic import BaseConfig, BaseModel, DirectoryPath
+from pydantic import BaseModel, ConfigDict, DirectoryPath
from esmerald.protocols.template import TemplateEngineProtocol
from esmerald.template.jinja import JinjaTemplateEngine
class TemplateConfig(BaseModel):
- class Config(BaseConfig):
- arbitrary_types_allowed = True
+ model_config = ConfigDict(arbitrary_types_allowed=True)
directory: Union[DirectoryPath, List[DirectoryPath]]
engine: Type[TemplateEngineProtocol] = JinjaTemplateEngine
diff --git a/esmerald/core/directives/base.py b/esmerald/core/directives/base.py
index bad3bbae..1b1db29f 100644
--- a/esmerald/core/directives/base.py
+++ b/esmerald/core/directives/base.py
@@ -4,18 +4,17 @@
from abc import ABC, abstractmethod
from typing import Any, Type
-from pydantic import BaseConfig, BaseModel
-
import esmerald
from esmerald.core.directives.exceptions import DirectiveError
from esmerald.core.directives.parsers import DirectiveParser
from esmerald.core.terminal.print import Print
+from esmerald.parsers import ArbitraryExtraBaseModel
from esmerald.utils.helpers import is_async_callable
printer = Print()
-class BaseDirective(BaseModel, ABC):
+class BaseDirective(ArbitraryExtraBaseModel, ABC):
"""The base class from which all directrives derive"""
help: str = ""
@@ -74,7 +73,3 @@ async def run(self, *args: Any, **options: Any) -> Any:
def handle(self, *args: Any, **options: Any) -> Any:
"""The logic of the directive. Subclasses must implement this method"""
raise NotImplementedError("subclasses of BaseDirective must provide a handle() method.")
-
- class Config(BaseConfig):
- extra = "allow" # type: ignore
- arbitrary_types_allowed = True
diff --git a/esmerald/core/directives/cli.py b/esmerald/core/directives/cli.py
index 67246668..8851bf65 100644
--- a/esmerald/core/directives/cli.py
+++ b/esmerald/core/directives/cli.py
@@ -90,7 +90,7 @@ def invoke(self, ctx: click.Context) -> typing.Any:
return super().invoke(ctx)
-@click.group(
+@click.group( # type: ignore
cls=DirectiveGroup,
)
@click.option(
diff --git a/esmerald/core/directives/operations/createapp.py b/esmerald/core/directives/operations/createapp.py
index 2fb047d7..5a77caa3 100644
--- a/esmerald/core/directives/operations/createapp.py
+++ b/esmerald/core/directives/operations/createapp.py
@@ -9,7 +9,7 @@
printer = Print()
-@click.option("-v", "--verbosity", default=1, type=int, help="Displays the files generated")
+@click.option("-v", "--verbosity", default=1, type=int, help="Displays the files generated") # type: ignore
@click.argument("name", type=str)
@click.command(name="createapp")
def create_app(name: str, verbosity: int) -> None:
diff --git a/esmerald/core/directives/operations/createproject.py b/esmerald/core/directives/operations/createproject.py
index a1bf6712..d65e2ece 100644
--- a/esmerald/core/directives/operations/createproject.py
+++ b/esmerald/core/directives/operations/createproject.py
@@ -9,7 +9,7 @@
printer = Print()
-@click.option("-v", "--verbosity", default=1, type=int, help="Displays the files generated")
+@click.option("-v", "--verbosity", default=1, type=int, help="Displays the files generated") # type: ignore
@click.argument("name", type=str)
@click.command(name="createproject")
def create_project(name: str, verbosity: int) -> None:
diff --git a/esmerald/core/directives/operations/run.py b/esmerald/core/directives/operations/run.py
index b1d76ffa..4f9ae693 100644
--- a/esmerald/core/directives/operations/run.py
+++ b/esmerald/core/directives/operations/run.py
@@ -26,7 +26,7 @@ class Position(int, Enum):
BACK = 3
-@click.option(
+@click.option( # type: ignore
"--directive",
"directive",
required=True,
diff --git a/esmerald/core/directives/operations/runserver.py b/esmerald/core/directives/operations/runserver.py
index b341d6d4..94d4e91a 100644
--- a/esmerald/core/directives/operations/runserver.py
+++ b/esmerald/core/directives/operations/runserver.py
@@ -14,7 +14,7 @@
terminal = Terminal()
-@click.option(
+@click.option( # type: ignore
"-p",
"--port",
type=int,
diff --git a/esmerald/core/directives/templates.py b/esmerald/core/directives/templates.py
index c8bd3ab2..b7f5a744 100644
--- a/esmerald/core/directives/templates.py
+++ b/esmerald/core/directives/templates.py
@@ -21,9 +21,7 @@ class TemplateDirective(BaseDirective):
layout.
"""
- url_schemes = ["http", "https", "ftp"]
-
- rewrite_template_suffixes = (
+ rewrite_template_suffixes: Any = (
(".py-tpl", ".py"),
(".e-tpl", ""),
)
diff --git a/esmerald/datastructures/base.py b/esmerald/datastructures/base.py
index ee3c77f2..9eb46e32 100644
--- a/esmerald/datastructures/base.py
+++ b/esmerald/datastructures/base.py
@@ -3,8 +3,7 @@
from http.cookies import SimpleCookie
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar, Union
-from pydantic import BaseConfig, BaseModel, validator # noqa
-from pydantic.generics import GenericModel # noqa
+from pydantic import BaseModel, ConfigDict, field_validator # noqa
from starlette.datastructures import URL as URL # noqa: F401
from starlette.datastructures import Address as Address # noqa: F401
from starlette.datastructures import FormData as FormData # noqa: F401
@@ -83,17 +82,15 @@ def to_header(self, **kwargs: Any) -> str:
simple_cookie[self.key] = self.value or ""
if self.max_age:
simple_cookie[self.key]["max-age"] = self.max_age
- cookie_dict = self.dict()
+ cookie_dict = self.model_dump()
for key in ["expires", "path", "domain", "secure", "httponly", "samesite"]:
if cookie_dict[key] is not None:
simple_cookie[self.key][key] = cookie_dict[key]
return simple_cookie.output(**kwargs).strip()
-class ResponseContainer(GenericModel, ABC, Generic[R]):
- class Config(BaseConfig):
- arbitrary_types_allowed = True
-
+class ResponseContainer(BaseModel, ABC, Generic[R]):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
background: Optional[Union[BackgroundTask, BackgroundTasks]] = None
headers: Dict[str, Any] = {}
cookies: List[Cookie] = []
@@ -110,11 +107,9 @@ def to_response(
class ResponseHeader(BaseModel):
- value: Any = None
+ value: Optional[Any] = None
- @validator("value", always=True)
- def validate_value(
- cls, value: Any, values: Dict[str, Any]
- ) -> Any: # pylint: disable=no-self-argument
+ @field_validator("value") # type: ignore
+ def validate_value(cls, value: Any, values: Dict[str, Any]) -> Any:
if value is not None:
return value
diff --git a/esmerald/datastructures/encoders.py b/esmerald/datastructures/encoders.py
index 2d266515..f9173a90 100644
--- a/esmerald/datastructures/encoders.py
+++ b/esmerald/datastructures/encoders.py
@@ -18,6 +18,7 @@
class OrJSON(ResponseContainer[ORJSONResponse]):
+ media_type: str = "application/json"
content: Optional[Dict[str, Any]] = None
status_code: Optional[int] = None
@@ -53,6 +54,7 @@ def to_response(
class UJSON(ResponseContainer[UJSONResponse]):
+ media_type: str = "application/json"
content: Optional[Dict[str, Any]] = None
status_code: Optional[int] = None
diff --git a/esmerald/datastructures/file.py b/esmerald/datastructures/file.py
index cbb27de3..980b4400 100644
--- a/esmerald/datastructures/file.py
+++ b/esmerald/datastructures/file.py
@@ -1,7 +1,7 @@
import os
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union, cast
-from pydantic import FilePath, validator # noqa
+from pydantic import FilePath, field_validator, model_validator # noqa
from starlette.responses import FileResponse # noqa
from esmerald.datastructures.base import ResponseContainer
@@ -16,12 +16,11 @@ class File(ResponseContainer[FileResponse]):
filename: str
stat_result: Optional[os.stat_result] = None
- @validator("stat_result", always=True)
- def validate_status_code( # pylint: disable=no-self-argument
- cls, value: Optional[os.stat_result], values: Dict[str, Any]
- ) -> os.stat_result:
- """Set the stat_result value for the given filepath."""
- return value or os.stat(cast("str", values.get("path")))
+ @model_validator(mode="before")
+ def validate_fields(cls, values: Dict[str, Any]) -> Any:
+ stat_result = values.get("stat_result")
+ values["stat_result"] = stat_result or os.stat(cast("str", values.get("path")))
+ return values
def to_response(
self,
diff --git a/esmerald/datastructures/json.py b/esmerald/datastructures/json.py
index ba199cea..ab5e76ca 100644
--- a/esmerald/datastructures/json.py
+++ b/esmerald/datastructures/json.py
@@ -15,6 +15,7 @@ class JSON(ResponseContainer[JSONResponse]):
content: Optional[Dict[str, Any]] = None
status_code: Optional[int] = None
+ media_type: str = "application/json"
def __init__(
self,
@@ -25,6 +26,7 @@ def __init__(
super().__init__(**kwargs)
self.content = content
self.status_code = status_code
+ self._media_type = self.media_type
def to_response(
self,
diff --git a/esmerald/datastructures/stream.py b/esmerald/datastructures/stream.py
index 3226d68d..3428a200 100644
--- a/esmerald/datastructures/stream.py
+++ b/esmerald/datastructures/stream.py
@@ -25,11 +25,8 @@
class Stream(ResponseContainer[StreamingResponse]):
iterator: Union[
Iterator[Union[str, bytes]],
- Generator[Union[str, bytes], Any, Any],
AsyncIterator[Union[str, bytes]],
AsyncGenerator[Union[str, bytes], Any],
- Type[Iterator[Union[str, bytes]]],
- Type[AsyncIterator[Union[str, bytes]]],
Callable[[], AsyncGenerator[Union[str, bytes], Any]],
Callable[[], Generator[Union[str, bytes], Any, Any]],
]
diff --git a/esmerald/enums.py b/esmerald/enums.py
index 9d133354..2ce3a4df 100644
--- a/esmerald/enums.py
+++ b/esmerald/enums.py
@@ -18,6 +18,7 @@ class MediaType(str, Enum):
TEXT = "text/plain"
TEXT_CHARSET = "text/plain; charset=utf-8"
PNG = "image/png"
+ OCTET = "application/octet-stream"
class OpenAPIMediaType(str, Enum):
diff --git a/esmerald/exception_handlers.py b/esmerald/exception_handlers.py
index 216ff179..501501dc 100644
--- a/esmerald/exception_handlers.py
+++ b/esmerald/exception_handlers.py
@@ -1,6 +1,6 @@
from typing import Union
-from pydantic.error_wrappers import ValidationError
+from pydantic import ValidationError
from starlette import status
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.requests import Request
diff --git a/esmerald/exceptions.py b/esmerald/exceptions.py
index 26b0105b..54aefece 100644
--- a/esmerald/exceptions.py
+++ b/esmerald/exceptions.py
@@ -117,7 +117,7 @@ class MissingDependency(EsmeraldAPIException, ImportError):
...
-class OpenAPIError(ValueError):
+class OpenAPIException(ImproperlyConfigured):
...
diff --git a/esmerald/injector.py b/esmerald/injector.py
index 3e84f987..3030baa5 100644
--- a/esmerald/injector.py
+++ b/esmerald/injector.py
@@ -6,7 +6,7 @@
from esmerald.utils.helpers import is_async_callable
if TYPE_CHECKING:
- from pydantic.typing import AnyCallable
+ from esmerald.typing import AnyCallable
class Inject(ArbitraryHashableBaseModel):
diff --git a/esmerald/middleware/authentication.py b/esmerald/middleware/authentication.py
index 11bfc0f4..4875b34b 100644
--- a/esmerald/middleware/authentication.py
+++ b/esmerald/middleware/authentication.py
@@ -1,22 +1,19 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
-from pydantic import BaseConfig, BaseModel
from starlette.requests import HTTPConnection
from esmerald.enums import ScopeType
+from esmerald.parsers import ArbitraryBaseModel
from esmerald.protocols.middleware import MiddlewareProtocol
if TYPE_CHECKING:
from starlette.types import ASGIApp, Receive, Scope, Send
-class AuthResult(BaseModel):
+class AuthResult(ArbitraryBaseModel):
user: Any
- class Config(BaseConfig):
- arbitrary_types_allowed = True
-
class BaseAuthMiddleware(ABC, MiddlewareProtocol):
scopes = {ScopeType.HTTP, ScopeType.WEBSOCKET}
diff --git a/esmerald/openapi/_internal.py b/esmerald/openapi/_internal.py
new file mode 100644
index 00000000..0c5a227d
--- /dev/null
+++ b/esmerald/openapi/_internal.py
@@ -0,0 +1,20 @@
+from inspect import Signature
+from typing import Any, Optional, Union
+
+from pydantic import BaseModel, ConfigDict
+
+from esmerald.enums import MediaType
+
+
+class InternalResponse(BaseModel):
+ """
+ Response generated for non common return types.
+ """
+
+ media_type: Optional[Union[str, MediaType]] = None
+ return_annotation: Optional[Any] = None
+ signature: Optional[Signature] = None
+ description: Optional[str] = None
+ encoding: Optional[str] = None
+
+ model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
diff --git a/esmerald/openapi/apiview.py b/esmerald/openapi/apiview.py
deleted file mode 100644
index 654c5802..00000000
--- a/esmerald/openapi/apiview.py
+++ /dev/null
@@ -1,354 +0,0 @@
-from json import dumps
-from typing import TYPE_CHECKING, Callable, Dict
-
-from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND
-
-from esmerald.enums import MediaType, OpenAPIMediaType
-from esmerald.exceptions import ImproperlyConfigured
-from esmerald.requests import Request
-from esmerald.responses import Response
-from esmerald.routing.handlers import get
-from esmerald.routing.views import APIView
-
-if TYPE_CHECKING:
- from openapi_schemas_pydantic.v3_1_0.open_api import OpenAPI
- from typing_extensions import Literal
-
-MSG_OPENAPI_NOT_INITIALIZED = "Esmerald has not been created with an OpenAPIConfig"
-
-
-class OpenAPIView(APIView):
- """
- The view that manages the OpenAPI documentation
- """
-
- path: str = "/docs"
- style: str = "body { margin: 0; padding: 0 }"
- redoc_version: str = "next"
- swagger_ui_version: str = "4.14.0"
- stoplight_elements_version: str = "7.6.5"
- favicon_url: str = "https://esmerald.dymmond.com/statics/images/favicon.ico"
- redoc_google_fonts: bool = True
- redoc_js_url: str = (
- f"https://cdn.jsdelivr.net/npm/redoc@{redoc_version}/bundles/redoc.standalone.js"
- )
- swagger_css_url: str = (
- f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{swagger_ui_version}/swagger-ui.css"
- )
- swagger_ui_bundle_js_url: str = (
- f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{swagger_ui_version}/swagger-ui-bundle.js"
- )
- swagger_ui_standalone_preset_js_url: str = f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{swagger_ui_version}/swagger-ui-standalone-preset.js"
- stoplight_elements_css_url: str = (
- f"https://unpkg.com/@stoplight/elements@{stoplight_elements_version}/styles.min.css"
- )
- stoplight_elements_js_url: str = (
- f"https://unpkg.com/@stoplight/elements@{stoplight_elements_version}/web-components.min.js"
- )
-
- _dumped_schema: str = ""
- _dumped_modified_schema: str = ""
-
- @staticmethod
- def get_schema_from_request(request: Request) -> "OpenAPI":
- if not request.app.openapi_schema:
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
- return request.app.openapi_schema
-
- def should_serve_endpoint(self, request: Request) -> bool:
- if not request.app.openapi_config:
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- request_path = set(filter(None, request.url.path.split("/")))
- root_path = request.app.root_path or set(filter(None, self.path.split("/")))
-
- config = request.app.openapi_config
-
- if request_path == root_path and config.root_schema_site in config.enabled_endpoints:
- return True
-
- if request_path & config.enabled_endpoints:
- return True
-
- return False
-
- @property
- def favicon(self) -> str:
- return (
- f""
- if self.favicon_url
- else ""
- )
-
- @property
- def render_methods_map(
- self,
- ) -> Dict["Literal['redoc', 'swagger', 'elements']", Callable[[Request], str]]:
- """
- Returns:
- A mapping of string keys to render methods.
- """
- return {
- "redoc": self.render_redoc,
- "swagger": self.render_swagger_ui,
- "elements": self.render_stoplight_elements,
- }
-
- @get(
- path="/openapi.yaml",
- media_type=OpenAPIMediaType.OPENAPI_YAML,
- include_in_schema=False,
- )
- def retrieve_schema_yaml(self, request: Request) -> Response:
- if not request.app.openapi_config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- if self.should_serve_endpoint(request):
- return Response(
- content=self.get_schema_from_request(request),
- status_code=HTTP_200_OK,
- media_type=OpenAPIMediaType.OPENAPI_YAML,
- )
- return Response(
- content={},
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.JSON,
- )
-
- @get(
- path="/openapi.json",
- media_type=OpenAPIMediaType.OPENAPI_JSON,
- include_in_schema=False,
- )
- def retrieve_schema_json(self, request: Request) -> Response:
- if not request.app.openapi_config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- if self.should_serve_endpoint(request):
- return Response(
- content=self.get_schema_from_request(request),
- status_code=HTTP_200_OK,
- media_type=OpenAPIMediaType.OPENAPI_JSON,
- )
- return Response(
- content={},
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.JSON,
- )
-
- @get(path="/", media_type=MediaType.HTML, include_in_schema=False)
- def root(self, request: Request) -> Response:
- config = request.app.openapi_config
- if not config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
- render_method = self.render_methods_map[config.root_schema_site]
-
- if self.should_serve_endpoint(request):
- return Response(
- content=render_method(request),
- status_code=HTTP_200_OK,
- media_type=MediaType.HTML,
- )
- return Response(
- content=self.render_404_page(),
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.HTML,
- )
-
- @get(path="/swagger", media_type=MediaType.HTML, include_in_schema=False)
- def swagger_ui(self, request: Request) -> Response:
- if not request.app.openapi_config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- if self.should_serve_endpoint(request):
- return Response(
- content=self.render_swagger_ui(request),
- status_code=HTTP_200_OK,
- media_type=MediaType.HTML,
- )
- return Response(
- content=self.render_404_page(),
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.HTML,
- )
-
- @get(path="/elements", media_type=MediaType.HTML, include_in_schema=False)
- def stoplight_elements(self, request: Request) -> Response:
- if not request.app.openapi_config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- if self.should_serve_endpoint(request):
- return Response(
- content=self.render_stoplight_elements(request),
- status_code=HTTP_200_OK,
- media_type=MediaType.HTML,
- )
- return Response(
- content=self.render_404_page(),
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.HTML,
- )
-
- @get(path="/redoc", media_type=MediaType.HTML, include_in_schema=False)
- def redoc(self, request: Request) -> Response: # pragma: no cover
- if not request.app.openapi_config: # pragma: no cover
- raise ImproperlyConfigured(MSG_OPENAPI_NOT_INITIALIZED)
-
- if self.should_serve_endpoint(request):
- return Response(
- content=self.render_redoc(request),
- status_code=HTTP_200_OK,
- media_type=MediaType.HTML,
- )
- return Response(
- content=self.render_404_page(),
- status_code=HTTP_404_NOT_FOUND,
- media_type=MediaType.HTML,
- )
-
- def render_swagger_ui(self, request: Request) -> str:
- schema = self.get_schema_from_request(request)
- # Note: Fix for Swagger rejection OpenAPI >=3.1
- if self._dumped_modified_schema == "":
- schema_copy = schema.copy()
- schema_copy.openapi = "3.0.3"
- self._dumped_modified_schema = dumps(
- schema_copy.json(by_alias=True, exclude_none=True),
- )
-
- head = f"""
-