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

Release 1.7.0 #120

Merged
merged 34 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b43db93
:sparkles: Decouple table manager logic from repositories (#118)
perdy Oct 5, 2023
73708b7
:sparkles: Enhanced OpenAPI docstrings (#121)
perdy Oct 6, 2023
f49b290
:sparkles: Enhanced actions for DDD repositories (#124)
perdy Oct 8, 2023
973872d
:label: Remove types.Tag
perdy Oct 8, 2023
4ed1019
:label: Fix middleware types
perdy Oct 8, 2023
8d8a480
:sparkles: ASGI Cookies component
perdy Oct 10, 2023
6ec97ef
:package: Include templates building as part of poetry build
perdy Oct 10, 2023
4deb597
:loud_sound: Logging traces to debug middlewares
perdy Oct 13, 2023
fa84068
:bug: Fix resolution for routes within nested resources
perdy Oct 14, 2023
fa71dbc
:bug: Include nested applications in Lifespan (#130)
perdy Oct 16, 2023
a8f2d88
:recycle: DDD worker resolution using root_app
perdy Oct 18, 2023
06527b2
:recycle: Drop sync client
perdy Oct 18, 2023
649ac27
:sparkles: Python 3.12 compatibility (#119)
perdy Oct 18, 2023
4081dd3
:sparkles: Single type injection (#126)
perdy Oct 19, 2023
cedd69b
:sparkles: Connections manager for SQLAlchemy module (#135)
perdy Oct 22, 2023
bdfda0c
:recycle: Remove some tests warnings
perdy Oct 23, 2023
82e7753
:sparkles: Authentication mechanism (#137)
perdy Oct 27, 2023
8e53bb0
:sparkles: Ordering for repositories list (#139)
perdy Nov 13, 2023
80b8090
:art: Enhance CRUD method docs
perdy Nov 17, 2023
2136f5e
:sparkles: Enhanced operations for CRUD (#141)
perdy Nov 23, 2023
d7b9a37
:label: Fix APIResponse types
perdy Nov 29, 2023
66e472c
:bug: Resolve url match multiple path params
perdy Nov 29, 2023
d4d8e07
:bug: Remove unintended sqlalchemy import (#143)
perdy Dec 22, 2023
b14d32c
:bug: Serve flama logo from its repository (#144)
perdy Dec 22, 2023
d1ec55c
:memo: Add star history graph
migduroli Feb 10, 2024
b9c20b8
:sparkles: Non-blocking errors on schema generation
perdy Apr 11, 2024
2ec6c36
:sparkles: Implement DDD for HTTP resources
migduroli Apr 15, 2024
ec4ae36
:bug: Accept empty body as a valid input for schema validation
perdy Apr 25, 2024
a24a583
:sparkles: Use PartialSchema for all-optional schema version
perdy May 3, 2024
8ae53dc
:sparkles: HTTPWorker accepts client kwargs
perdy May 6, 2024
bb39e58
:bug: Fix middleware types
perdy May 10, 2024
e9aa6aa
:bug: Allow nullable nested schemas for Pydantic
perdy May 12, 2024
d22272f
:bug: Atomic operations on SQLAlchemy connections management
perdy May 13, 2024
879cdc9
:hammer: JWT decoding error raises unauthorized exception
migduroli Jul 9, 2024
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
6 changes: 3 additions & 3 deletions .github/workflows/test_and_publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

env:
DEFAULT_LINUX: "slim"
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.12"
DEFAULT_SCHEMAS: "pydantic"

jobs:
Expand All @@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: [ "3.8", "3.9", "3.10", "3.11"]
python: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
container:
image: python:${{ matrix.python }}
steps:
Expand Down Expand Up @@ -100,7 +100,7 @@ jobs:
strategy:
matrix:
linux: ["slim"]
python: ["3.8", "3.9", "3.10", "3.11"]
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
schemas: ["pydantic", "marshmallow", "typesystem"]
steps:
- name: Check out the repo
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_pull_request_branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.8", "3.9", "3.10", "3.11"]
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
container:
image: python:${{ matrix.python }}
steps:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,13 @@ flama run examples.hello_flama:app
This project is absolutely open to contributions so if you have a nice idea, please read
our [contributing docs](.github/CONTRIBUTING.md) **before submitting** a pull
request.

## Star History

<a href="https://github.com/vortico/flama">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=vortico/flama&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=vortico/flama&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=vortico/flama&type=Date" />
</picture>
</a>
12 changes: 12 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import subprocess


def build():
print("🔥 Install js requirements...")
subprocess.run(["npm", "install"], cwd="templates")
print("🔥 Build templates...")
subprocess.run(["npm", "run", "build"], cwd="templates")


if __name__ == "__main__":
build()
32 changes: 8 additions & 24 deletions examples/resource.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import databases
import sqlalchemy
from pydantic import BaseModel
from sqlalchemy import create_engine

import flama
from flama import Flama
from flama.resources.crud import CRUDListResourceType
from flama.resources.crud import CRUDResource
from flama.sqlalchemy import SQLAlchemyModule

DATABASE_URL = "sqlite:///resource.db"

database = databases.Database(DATABASE_URL)
DATABASE_URL = "sqlite+aiosqlite://"

metadata = sqlalchemy.MetaData()

Expand All @@ -27,9 +24,7 @@ class PuppySchema(BaseModel):
)


class PuppyResource(metaclass=CRUDListResourceType):
database = database

class PuppyResource(CRUDResource):
name = "puppy"
verbose_name = "Puppy"

Expand All @@ -39,24 +34,13 @@ class PuppyResource(metaclass=CRUDListResourceType):

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

app.add_resource("/", PuppyResource)


@app.on_event("startup")
async def startup():
engine = create_engine(DATABASE_URL)
metadata.create_all(engine) # Create the tables.
await database.connect()


@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
app.resources.add_resource("/", PuppyResource)


if __name__ == "__main__":
flama.run(app, host="0.0.0.0", port=8000)
flama.run(flama_app=app, server_host="0.0.0.0", server_port=8080)
56 changes: 45 additions & 11 deletions flama/applications.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import functools
import logging
import threading
import typing as t

from flama import asgi, http, injection, types, url, validation, websockets
from flama.ddd.components import WorkerComponent
from flama import asgi, exceptions, http, injection, types, url, validation, websockets
from flama.events import Events
from flama.middleware import MiddlewareStack
from flama.models.modules import ModelsModule
Expand All @@ -12,6 +13,13 @@
from flama.routing import BaseRoute, Router
from flama.schemas.modules import SchemaModule

try:
from flama.ddd.components import WorkerComponent
from flama.resources.workers import FlamaWorker
except AssertionError:
WorkerComponent = None
FlamaWorker = None

if t.TYPE_CHECKING:
from flama.middleware import Middleware
from flama.modules import Module
Expand All @@ -20,6 +28,8 @@

__all__ = ["Flama"]

logger = logging.getLogger(__name__)


class Flama:
def __init__(
Expand Down Expand Up @@ -57,7 +67,7 @@ def __init__(
:param schema_library: Schema library to use.
"""
self._debug = debug
self._status = types.AppStatus.NOT_INITIALIZED
self._status = types.AppStatus.NOT_STARTED
self._shutdown = False

# Create Dependency Injector
Expand All @@ -79,22 +89,28 @@ def __init__(
}
)

# Create worker
worker = FlamaWorker() if FlamaWorker else None

# Initialize Modules
default_modules = [
ResourcesModule(),
ResourcesModule(worker=worker),
SchemaModule(title, version, description, schema=schema, docs=docs),
ModelsModule(),
]
self.modules = Modules(app=self, modules={*default_modules, *(modules or [])})

# Initialize router
default_components = [WorkerComponent(worker=default_modules[0].worker)]
default_components = []
if worker and WorkerComponent:
default_components.append(WorkerComponent(worker=worker))

self.app = self.router = Router(
routes=routes, components=[*default_components, *(components or [])], lifespan=lifespan
)

# Build middleware stack
self.middleware = MiddlewareStack(app=self.app, middleware=middleware or [], debug=debug)
self.middleware = MiddlewareStack(app=self, middleware=middleware or [], debug=debug)

# Setup schema library
self.schema.schema_library = schema_library
Expand Down Expand Up @@ -131,9 +147,27 @@ async def __call__(self, scope: types.Scope, receive: types.Receive, send: types
:param receive: ASGI receive event.
:param send: ASGI send event.
"""
if scope["type"] != "lifespan" and self.status in (types.AppStatus.NOT_STARTED, types.AppStatus.STARTING):
raise exceptions.ApplicationError("Application is not ready to process requests yet.")

if scope["type"] != "lifespan" and self.status in (types.AppStatus.SHUT_DOWN, types.AppStatus.SHUTTING_DOWN):
raise exceptions.ApplicationError("Application is already shut down.")

scope["app"] = self
scope.setdefault("root_app", self)
await self.middleware(scope, receive, send)

@property
def status(self) -> types.AppStatus:
return self._status

@status.setter
def status(self, s: types.AppStatus) -> None:
logger.debug("Transitioning %s from %s to %s", self, self._status, s)

with threading.Lock():
self._status = s

@property
def components(self) -> injection.Components:
"""Components register.
Expand Down Expand Up @@ -167,7 +201,7 @@ def add_route(
include_in_schema: bool = True,
route: t.Optional["Route"] = None,
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
tags: t.Optional[t.Dict[str, types.Tag]] = None,
tags: t.Optional[t.Dict[str, t.Any]] = None,
) -> "Route":
"""Register a new HTTP route or endpoint under given path.

Expand Down Expand Up @@ -199,7 +233,7 @@ def route(
name: t.Optional[str] = None,
include_in_schema: bool = True,
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
tags: t.Optional[t.Dict[str, types.Tag]] = None,
tags: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Callable[[types.HTTPHandler], types.HTTPHandler]:
"""Decorator version for registering a new HTTP route in this router under given path.

Expand Down Expand Up @@ -228,7 +262,7 @@ def add_websocket_route(
name: t.Optional[str] = None,
route: t.Optional["WebSocketRoute"] = None,
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
tags: t.Optional[t.Dict[str, types.Tag]] = None,
tags: t.Optional[t.Dict[str, t.Any]] = None,
) -> "WebSocketRoute":
"""Register a new websocket route or endpoint under given path.

Expand All @@ -248,7 +282,7 @@ def websocket_route(
path: str,
name: t.Optional[str] = None,
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
tags: t.Optional[t.Dict[str, types.Tag]] = None,
tags: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Callable[[types.WebSocketHandler], types.WebSocketHandler]:
"""Decorator version for registering a new websocket route in this router under given path.

Expand All @@ -266,7 +300,7 @@ def mount(
app: t.Optional[types.App] = None,
name: t.Optional[str] = None,
mount: t.Optional["Mount"] = None,
tags: t.Optional[t.Dict[str, types.Tag]] = None,
tags: t.Optional[t.Dict[str, t.Any]] = None,
) -> "Mount":
"""Register a new mount point containing an ASGI app in this router under given path.

Expand Down
20 changes: 17 additions & 3 deletions flama/asgi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from http.cookies import SimpleCookie
from urllib.parse import parse_qsl

from flama import types
from flama import http, types
from flama.injection.components import Component, Components

__all__ = [
Expand Down Expand Up @@ -66,8 +67,20 @@ def resolve(self, scope: types.Scope) -> types.QueryParams:


class HeadersComponent(Component):
def resolve(self, scope: types.Scope) -> types.Headers:
return types.Headers(scope=scope)
def resolve(self, request: http.Request) -> types.Headers:
return request.headers


class CookiesComponent(Component):
def resolve(self, headers: types.Headers) -> types.Cookies:
cookie = SimpleCookie()
cookie.load(headers.get("cookie", ""))
return types.Cookies(
{
str(name): {**{str(k): str(v) for k, v in morsel.items()}, "value": morsel.value}
for name, morsel in cookie.items()
}
)


class BodyComponent(Component):
Expand Down Expand Up @@ -95,6 +108,7 @@ async def resolve(self, receive: types.Receive) -> types.Body:
QueryStringComponent(),
QueryParamsComponent(),
HeadersComponent(),
CookiesComponent(),
BodyComponent(),
]
)
21 changes: 0 additions & 21 deletions flama/authentication.py

This file was deleted.

3 changes: 3 additions & 0 deletions flama/authentication/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flama.authentication.components import * # noqa
from flama.authentication.jwt import * # noqa
from flama.authentication.middlewares import * # noqa
Loading
Loading