-
Hi, i'm wondering how to integrate falcon with Tortoise ORM. As far as i know, Tortoise ORM manages transactions on its own, but i'm not sure if it creates a transaction for each request (although i don't see why it would). I already managed to create Here you can see an example. There’s one problem: @before(authorize_user, is_logged=True, is_email_verified=False)
class VerifyEmailResource:
async def on_get(self, req: Request, resp: Response):
entity_user: UserType = req.context.entity_user # Got from authorize_user. (Used Tortoise ORM)
async with in_transaction():
await entity_user.verification().start_verification() # Tortoise ORM operations. I'm thinking about creating a decorator to wrap Current middleware: class TortoiseMiddleware:
async def process_startup(self, scope, event):
await Tortoise.init(config=TORTOISE_ORM)
await Tortoise.generate_schemas()
async def process_shutdown(self, scope, event):
await Tortoise.close_connections() I would really appreciate help with the integration. Also if you have a good solution for other ORM integration, please feel free to share it. |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 4 replies
-
Ok, so i created a decorator, def create_transaction():
def decorator(func):
@wraps(func)
async def wrapped(self, req, resp, *args, **kwargs):
async with in_transaction() as connection:
req.context.connection = connection
return await func(self, req, resp, *args, **kwargs)
return wrapped
return decorator class VerifyEmailResource:
@create_transaction()
@before(authorize_user, is_logged=True, is_email_verified=False) # uses req.context.connection
async def on_get(self, req: Request, resp: Response):
entity_user: UserType = req.context.entity_user
await entity_user.verification().start_verification()
resp.status = 200
resp.media = {
"title": VERIFICATION_STARTED_TITLE,
"description": VERIFICATION_STARTED_MSG
}
@create_transaction()
@before(authorize_user, is_logged=True, is_email_verified=False) # uses req.context.connection
@before(
validate_request,
token={
'type': str,
'validators': {
'exact_length': VERIFICATION_TOKEN_LENGTH
}
}
)
async def on_post(self, req: Request, resp: Response):
entity_user: UserType = req.context.entity_user
await entity_user.verification().end_verification(
token=req.context.data['token']
)
resp.status = 200
resp.media = {
"title": VERIFICATION_ENDED_TITLE,
"description": VERIFICATION_ENDED_MSG
} A much better option would be to do something like this under. However, i'm not sure how to implement the decorator properly to map all routes, like @create_transaction()
@before(authorize_user, is_logged=True, is_email_verified=False) # uses req.context.connection
class VerifyEmailResource:
async def on_get(self, req: Request, resp: Response):
entity_user: UserType = req.context.entity_user
await entity_user.verification().start_verification()
resp.status = 200
resp.media = {
"title": VERIFICATION_STARTED_TITLE,
"description": VERIFICATION_STARTED_MSG
}
@before(
validate_request,
token={
'type': str,
'validators': {
'exact_length': VERIFICATION_TOKEN_LENGTH
}
}
)
async def on_post(self, req: Request, resp: Response):
entity_user: UserType = req.context.entity_user
await entity_user.verification().end_verification(
token=req.context.data['token']
)
resp.status = 200
resp.media = {
"title": VERIFICATION_ENDED_TITLE,
"description": VERIFICATION_ENDED_MSG
} |
Beta Was this translation helpful? Give feedback.
-
Hi, This looks like a discussion, so I will move it there. The method are documented here process_resource and process_response. See the docs here https://falcon.readthedocs.io/en/stable/api/middleware.html class Middleware:
async def process_resource(
self,
req: Request,
resp: Response,
resource: object,
params: dict[str, Any],
) -> None:
req.context.conn = await new_transaction() # create a transaction
async def process_response(
self,
req: Request,
resp: Response,
resource: object,
req_succeeded: bool
) -> None:
if req_succeded:
await req.context.conn.commit()
else:
await req.context.conn.rollback() |
Beta Was this translation helpful? Give feedback.
-
Hi. I ended up with this solution. Tortoise ORM natively supports wrapping a function in a single transaction using the Atomic decorator. To ensure a request is in an independent transaction (and will roll back in case of an exception), you need to wrap it with from tortoise import Tortoise, fields
from tortoise.models import Model
from tortoise.transactions import atomic as req_in_transaction
class TestModel(Model):
uuid = fields.UUIDField(primary_key=True)
class TortoiseMiddleware:
async def process_startup(self, scope, event):
await Tortoise.init(config=TORTOISE_ORM)
await Tortoise.generate_schemas()
async def process_shutdown(self, scope, event):
await Tortoise.close_connections()
class TransactionTestResource:
@req_in_transaction() # Request is in one transaction.
async def on_post(self, req, resp):
db_test = TestModel()
await db_test.save()
assert 2 == 3 # Because of the exception db_test won't be saved (session.rollback). Without atomic (req_in_transaction) decorator, db_test would be saved.
resp.status = 200
resp.media = {
'message': 'Test'
} |
Beta Was this translation helpful? Give feedback.
-
Hi @0x1618, import contextlib
import logging
import falcon.asgi
logging.basicConfig(
format='%(asctime)s [%(levelname)s] %(message)s', level=logging.INFO)
@contextlib.asynccontextmanager
async def in_transaction():
logging.info('Entering async context...')
try:
yield 'Hello, World!'
finally:
logging.info('Finally leaving async context...')
class AsyncContextMiddleware:
async def process_request(self, req, resp):
req.context.exit_stack = contextlib.AsyncExitStack()
async def process_resource(self, req, resp, resource, params):
await req.context.exit_stack.enter_async_context(in_transaction())
async def process_response(self, req, resp, resource, req_succeeded):
await req.context.exit_stack.aclose()
class RequestLog:
async def process_response(self, req, resp, resource, req_succeeded):
logging.info(f'{req.method} {req.path} ==> HTTP {resp.status_code}')
class HelloResource:
async def on_get(self, req, resp):
resp.media = {'message': 'Hello, World!'}
app = falcon.asgi.App(middleware=[RequestLog(), AsyncContextMiddleware()])
app.add_route('/hello', HelloResource()) And then running it with Uvicorn:
If desired, you could also wrap the closing part with async def process_response(self, req, resp, resource, req_succeeded):
try:
await req.context.exit_stack.aclose()
except Exception:
logging.exception('Error closing exit stack') If you don't do anything special, Falcon should render a generic 500 for you on exceptions both when entering and leaving the context. |
Beta Was this translation helpful? Give feedback.
Hi. I ended up with this solution. Tortoise ORM natively supports wrapping a function in a single transaction using the Atomic decorator. To ensure a request is in an independent transaction (and will roll back in case of an exception), you need to wrap it with
tortoise.transactions.atomic
.