Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enrich OpenAPI and new docs page #182

Merged
merged 2 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ and production-ready services, offering automatic deployment for ML models.

Some remarkable characteristics:

* Generic classes for API resources with the convenience of standard CRUD methods over SQLAlchemy tables.
* A schema system (based on Marshmallow or Typesystem) which allows the declaration of inputs and outputs of endpoints
- Generic classes for API resources with the convenience of standard CRUD methods over SQLAlchemy tables.
- A schema system (based on Marshmallow or Typesystem) which allows the declaration of inputs and outputs of endpoints
very easily, with the convenience of reliable and automatic data-type validation.
* Dependency injection to make ease the process of managing parameters needed in endpoints via the use of `Component`s.
- Dependency injection to make ease the process of managing parameters needed in endpoints via the use of `Component`s.
Flama ASGI objects like `Request`, `Response`, `Session` and so on are defined as `Component`s ready to be injected in
your endpoints.
* `Component`s as the base of the plugin ecosystem, allowing you to create custom or use those already defined in your
- `Component`s as the base of the plugin ecosystem, allowing you to create custom or use those already defined in your
endpoints, injected as parameters.
* Auto generated API schema using OpenAPI standard.
* Auto generated `docs`, and provides a Swagger UI and ReDoc endpoints.
* Automatic handling of pagination, with several methods at your disposal such as `limit-offset` and `page numbering`,
- Auto generated API schema using OpenAPI standard.
- Auto generated `docs`, and provides a Swagger UI and ReDoc endpoints.
- Automatic handling of pagination, with several methods at your disposal such as `limit-offset` and `page numbering`,
to name a few.

## Installation
Expand All @@ -52,7 +52,7 @@ Flama is fully compatible with all [supported versions](https://devguide.python.
you to use the latest version available.

For a detailed explanation on how to install flama
visit: [https://flama.dev/docs/getting-started/installation](https://flama.dev/docs/getting-started/installation).
visit: [https://flama.dev/docs/getting-started/installation](https://flama.dev/docs/getting-started/installation).

## Getting Started

Expand All @@ -68,11 +68,7 @@ Visit [https://flama.dev/docs/](https://flama.dev/docs/) to view the full docume
```python
from flama import Flama

app = Flama(
title="Hello-🔥",
version="1.0",
description="My first API",
)
app = Flama()


@app.route("/")
Expand Down Expand Up @@ -101,8 +97,8 @@ flama run examples.hello_flama:app

## Authors

* José Antonio Perdiguero López ([@perdy](https://github.com/perdy/))
* Miguel Durán-Olivencia ([@migduroli](https://github.com/migduroli/))
- José Antonio Perdiguero López ([@perdy](https://github.com/perdy/))
- Miguel Durán-Olivencia ([@migduroli](https://github.com/migduroli/))

## Contributing

Expand Down
10 changes: 7 additions & 3 deletions examples/add_model_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,13 @@ def metadata(self):


app = Flama(
title="Flama ML",
version="0.1.0",
description="Machine learning API using Flama 🔥",
openapi={
"info": {
"title": "Flama ML",
"version": "0.1.0",
"description": "Machine learning API using Flama 🔥",
}
},
docs="/docs/",
components=[component],
)
Expand Down
10 changes: 7 additions & 3 deletions examples/add_model_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
from flama.resources import resource_method

app = Flama(
title="Flama ML",
version="0.1.0",
description="Machine learning API using Flama 🔥",
openapi={
"info": {
"title": "Flama ML",
"version": "0.1.0",
"description": "Machine learning API using Flama 🔥",
}
},
docs="/docs/",
)

Expand Down
10 changes: 7 additions & 3 deletions examples/add_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ def user(username: str):


app = Flama(
title="Flama ML",
version="0.1.0",
description="Machine learning API using Flama 🔥",
openapi={
"info": {
"title": "Flama ML",
"version": "0.1.0",
"description": "Machine learning API using Flama 🔥",
}
},
routes=[
routing.Route("/", home),
routing.Route("/user/me", user_me),
Expand Down
10 changes: 7 additions & 3 deletions examples/data_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
from flama import Flama, schemas

app = Flama(
title="Puppy Register", # API title
version="0.1", # API version
description="A register of puppies", # API description
openapi={
"info": {
"title": "Puppy Register", # API title
"version": "0.1", # API version
"description": "A register of puppies", # API description
}
},
schema="/schema/", # Path to expose OpenAPI schema
docs="/docs/", # Path to expose Docs application
)
Expand Down
13 changes: 10 additions & 3 deletions examples/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
from flama import Flama, routing

app = Flama(
title="Hello-🔥",
version="1.0",
description="My first API",
openapi={
"info": {
"title": "Hello-🔥",
"version": "1.0",
"description": "My first API",
},
"tags": [
{"name": "Salute", "description": "This is the salute description"},
],
},
debug=True,
)

Expand Down
13 changes: 12 additions & 1 deletion examples/hello_flama.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import flama

app = flama.Flama(title="Hello-🔥", version="1.0", description="My first API")
app = flama.Flama(
openapi={
"info": {
"title": "Hello-🔥",
"version": "1.0",
"description": "My first API",
},
"tags": [
{"name": "Salute", "description": "This is the salute description"},
],
}
)


@app.route("/")
Expand Down
10 changes: 7 additions & 3 deletions examples/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ def minimum_age_validation(cls, v):


app = Flama(
title="Puppy Register", # API title
version="0.1", # API version
description="A register of puppies", # API description
openapi={
"info": {
"title": "Puppy Register", # API title
"version": "0.1", # API version
"description": "A register of puppies", # API description
}
},
)


Expand Down
10 changes: 7 additions & 3 deletions examples/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ class PuppyResource(CRUDResource):


app = Flama(
title="Puppy Register", # API title
version="0.1.0", # API version
description="A register of puppies", # API description
openapi={
"info": {
"title": "Puppy Register", # API title
"version": "0.1.0", # API version
"description": "A register of puppies", # API description
}
},
modules=[SQLAlchemyModule(database=DATABASE_URL)],
)

Expand Down
14 changes: 10 additions & 4 deletions flama/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,22 @@
class Flama:
def __init__(
self,
*,
routes: t.Optional[t.Sequence["routing.BaseRoute"]] = None,
components: t.Optional[t.Union[t.Sequence[injection.Component], set[injection.Component]]] = None,
modules: t.Optional[t.Union[t.Sequence["Module"], set["Module"]]] = None,
middleware: t.Optional[t.Sequence["Middleware"]] = None,
debug: bool = False,
events: t.Optional[t.Union[dict[str, list[t.Callable[..., t.Coroutine[t.Any, t.Any, None]]]], Events]] = None,
lifespan: t.Optional[t.Callable[[t.Optional["Flama"]], t.AsyncContextManager]] = None,
title: str = "Flama",
version: str = "0.1.0",
description: str = "Firing up with the flame",
openapi: types.OpenAPISpec = {
"info": {
"title": "Flama",
"version": "0.1.0",
"summary": "Flama application",
"description": "Firing up with the flame",
},
},
schema: t.Optional[str] = "/schema/",
docs: t.Optional[str] = "/docs/",
schema_library: t.Optional[str] = None,
Expand Down Expand Up @@ -92,7 +98,7 @@ def __init__(
# Initialise modules
default_modules = [
ResourcesModule(worker=worker),
SchemaModule(title, version, description, schema=schema, docs=docs),
SchemaModule(openapi, schema=schema, docs=docs),
ModelsModule(),
]
self.modules = Modules(app=self, modules={*default_modules, *(modules or [])})
Expand Down
10 changes: 7 additions & 3 deletions flama/cli/templates/app.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ from flama import Flama

app = Flama(
debug={{ debug }},
title="{{ title }}",
version="{{ version }}",
description="{{ description }}",
openapi={
"info": {
"title": "{{ title }}",
"version": "{{ version }}",
"description": "{{ description }}",
}
},
schema="{{ schema }}",
docs="{{ docs }}"
)
Expand Down
10 changes: 9 additions & 1 deletion flama/compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys

__all__ = ["Concatenate", "ParamSpec", "TypeGuard", "UnionType", "StrEnum", "tomllib"]
__all__ = ["Concatenate", "ParamSpec", "TypeGuard", "UnionType", "NotRequired", "StrEnum", "tomllib"]

# PORT: Remove when stop supporting 3.9
# Concatenate was added in Python 3.10
Expand Down Expand Up @@ -37,6 +37,14 @@
else:
from typing import Union as UnionType

# PORT: Remove when stop supporting 3.10
# NotRequired was added in Python 3.11
# https://docs.python.org/3/library/enum.html#enum.StrEnum
if sys.version_info >= (3, 11):
from typing import NotRequired
else:
from typing_extensions import NotRequired


# PORT: Remove when stop supporting 3.10
# StrEnum was added in Python 3.11
Expand Down
31 changes: 2 additions & 29 deletions flama/schemas/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,35 +242,8 @@ def get_openapi_ref(


class SchemaGenerator:
def __init__(
self,
title: str,
version: str,
description: t.Optional[str] = None,
terms_of_service: t.Optional[str] = None,
contact_name: t.Optional[str] = None,
contact_url: t.Optional[str] = None,
contact_email: t.Optional[str] = None,
license_name: t.Optional[str] = None,
license_url: t.Optional[str] = None,
schemas: t.Optional[dict] = None,
):
contact = (
openapi.Contact(name=contact_name, url=contact_url, email=contact_email)
if contact_name or contact_url or contact_email
else None
)

license = openapi.License(name=license_name, url=license_url) if license_name else None

self.spec = openapi.OpenAPISpec(
title=title,
version=version,
description=description,
terms_of_service=terms_of_service,
contact=contact,
license=license,
)
def __init__(self, spec: types.OpenAPISpec, schemas: t.Optional[dict[str, schemas.Schema]] = None):
self.spec = openapi.OpenAPISpec.from_spec(spec)

# Builtin definitions
self.schemas = SchemaRegistry(schemas=schemas)
Expand Down
24 changes: 5 additions & 19 deletions flama/schemas/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pathlib import Path
from types import ModuleType

from flama import http, pagination, schemas
from flama import http, pagination, schemas, types
from flama.modules import Module
from flama.schemas.generator import SchemaGenerator

Expand All @@ -14,22 +14,13 @@
class SchemaModule(Module):
name = "schema"

def __init__(
self,
title: str,
version: str,
description: str,
schema: t.Optional[str] = None,
docs: t.Optional[str] = None,
):
def __init__(self, openapi: types.OpenAPISpec, *, schema: t.Optional[str] = None, docs: t.Optional[str] = None):
super().__init__()
# Schema definitions
self.schemas: dict[str, t.Any] = {}

# Schema
self.title = title
self.version = version
self.description = description
self.openapi = openapi
self.schema_path = schema
self.docs_path = docs

Expand All @@ -48,9 +39,7 @@ def schema_generator(self) -> SchemaGenerator:
:return: API Schema Generator.
"""
self.schemas.update({**schemas.schemas.SCHEMAS, **pagination.paginator.schemas})
return SchemaGenerator(
title=self.title, version=self.version, description=self.description, schemas=self.schemas
)
return SchemaGenerator(spec=self.openapi, schemas=self.schemas)

@property
def schema(self) -> dict[str, t.Any]:
Expand Down Expand Up @@ -81,13 +70,10 @@ def add_routes(self) -> None:
if self.schema_path:
self.app.add_route(self.schema_path, self.schema_view, methods=["GET"], include_in_schema=False)
if self.docs_path:
assert self.schema_path, "Schema path must be defined to use docs view"
self.app.add_route(self.docs_path, self.docs_view, methods=["GET"], include_in_schema=False)

def schema_view(self) -> http.OpenAPIResponse:
return http.OpenAPIResponse(self.schema)

def docs_view(self) -> http.HTMLResponse:
return http._FlamaTemplateResponse(
"schemas/docs.html", {"title": self.title, "schema_url": self.schema_path, "docs_url": self.docs_path}
)
return http._FlamaTemplateResponse("schemas/docs.html", {"schema": self.schema})
Loading