-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ded9e49
commit 7f5cda2
Showing
7 changed files
with
171 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,7 @@ | |
name = "fastapi-enterprise-template" | ||
version = "0.1.0" | ||
description = "cookiecutter enterprise template for fastapi" | ||
authors = [ | ||
{ name = "jeffry", email = "[email protected]" } | ||
] | ||
authors = [{ name = "jeffry", email = "[email protected]" }] | ||
dependencies = [ | ||
"fastapi>=0.108.0", | ||
"alembic>=1.13.1", | ||
|
@@ -13,7 +11,7 @@ dependencies = [ | |
"uvicorn[standard]>=0.25.0", | ||
"pyjwt>=2.8.0", | ||
"pandas>=2.1.4", | ||
"redis>=5.0.1", | ||
"redis>=5.0.6", | ||
"sentry_sdk>=1.39.2", | ||
"httpx>=0.26.0", | ||
"pydantic_extra_types>=2.4.1", | ||
|
@@ -23,6 +21,7 @@ dependencies = [ | |
"cryptography>=42.0.7", | ||
"bcrypt>=4.1.3", | ||
"gunicorn>=22.0.0", | ||
"aio-pika>=9.4.1", | ||
] | ||
readme = "README.md" | ||
requires-python = ">= 3.12" | ||
|
@@ -61,7 +60,24 @@ target-version = "py312" | |
|
||
[tool.ruff.lint] | ||
select = ["ALL"] | ||
ignore = ["D", "G002", "DTZ003", "ANN401", "ANN101", "ANN102", "EM101", "PD901", "COM812", "ISC001", "FBT", "A003", "PLR0913", "G004", "E501"] | ||
ignore = [ | ||
"D", | ||
"G002", | ||
"DTZ003", | ||
"ANN401", | ||
"ANN101", | ||
"ANN102", | ||
"EM101", | ||
"PD901", | ||
"COM812", | ||
"ISC001", | ||
"FBT", | ||
"A003", | ||
"PLR0913", | ||
"G004", | ||
"E501", | ||
"TRY003", | ||
] | ||
fixable = ["ALL"] | ||
|
||
|
||
|
@@ -73,12 +89,12 @@ fixable = ["ALL"] | |
"api.py" = ["A002", "B008"] | ||
"deps.py" = ["B008"] | ||
"src/internal/api.py" = ["ARG001"] | ||
"src/auth/schemas.py" = ["N815"] # frontend menu | ||
"src/auth/schemas.py" = ["N815"] # frontend menu | ||
"alembic/*.py" = ["INP001", "UP007"] | ||
"__init__.py" = ["F403"] | ||
|
||
[tool.ruff.lint.flake8-bugbear] | ||
extend-immutable-calls=[ | ||
extend-immutable-calls = [ | ||
"fastapi.Depends", | ||
"fastapi.Query", | ||
"fastapi.params_functions.Form", | ||
|
@@ -115,7 +131,7 @@ exclude_lines = [ | |
] | ||
|
||
[tool.coverage.run] | ||
concurrency=["thread", "greenlet"] | ||
concurrency = ["thread", "greenlet"] | ||
|
||
[tool.mypy] | ||
exclude = "^tools/.*" | ||
|
@@ -141,3 +157,5 @@ warn_untyped_fields = true | |
|
||
[tool.pyright] | ||
include = ["src", "tests", "examples"] | ||
reportIncompatibleVariableOverride = false | ||
pythonVersion = "3.12" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import TYPE_CHECKING, ClassVar | ||
|
||
if TYPE_CHECKING: | ||
from httpx import AsyncClient, Response | ||
|
||
|
||
class RabbitMQClient: | ||
headers: ClassVar[dict[str, str]] = {"content-type": "application/json"} | ||
|
||
def __init__(self, url: str, username: str, password: str) -> None: | ||
self.url: str = url | ||
self.auth: tuple[str, str] = (username, password) | ||
|
||
async def create_vhost(self, client: "AsyncClient", vhost: str) -> "Response": | ||
return await client.put(f"{self.url}/api/vhosts/{vhost}", auth=self.auth, headers=self.headers) | ||
|
||
async def delete_vhost(self, client: "AsyncClient", vhost: str) -> "Response": | ||
return await client.delete(f"{self.url}/api/vhosts/{vhost}", auth=self.auth, headers=self.headers) | ||
|
||
async def get_vhosts(self, client: "AsyncClient") -> "Response": | ||
return await client.get(f"{self.url}/api/vhosts", auth=self.auth, headers=self.headers) | ||
|
||
async def get_users(self, client: "AsyncClient") -> "Response": | ||
return await client.get(f"{self.url}/api/users", auth=self.auth, headers=self.headers) | ||
|
||
async def create_user(self, client: "AsyncClient", username: str, password: str, tags: str = "") -> "Response": | ||
return await client.put( | ||
f"{self.url}/api/users/{username}", | ||
auth=self.auth, | ||
headers=self.headers, | ||
json={"username": username, "password": password, "tags": tags}, | ||
) | ||
|
||
async def delete_user(self, client: "AsyncClient", username: str) -> "Response": | ||
return await client.delete(f"{self.url}/api/users/{username}", auth=self.auth, headers=self.headers) | ||
|
||
async def create_vhost_permission( | ||
self, client: "AsyncClient", vhost: str, username: str, configure: str, write: str, read: str | ||
) -> "Response": | ||
return await client.put( | ||
f"{self.url}/api/permissions/{vhost}/{username}", | ||
auth=self.auth, | ||
headers=self.headers, | ||
json={"configure": configure, "write": write, "read": read, "username": username, "vhost": vhost}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import json | ||
import logging | ||
from dataclasses import dataclass | ||
|
||
from aio_pika import Message, connect_robust | ||
from aio_pika.abc import AbstractRobustChannel, AbstractRobustConnection | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass | ||
class RabbitMQSession: | ||
connection: AbstractRobustConnection | None = None | ||
channel: AbstractRobustChannel | None = None | ||
|
||
@property | ||
async def _is_open(self) -> bool: | ||
"""Check if the session is open.""" | ||
return ( | ||
self.connection is not None | ||
and not self.connection.is_closed | ||
and self.channel is not None | ||
and not self.channel.is_closed | ||
) | ||
|
||
async def _close(self) -> None: | ||
if self.channel and not self.channel.is_closed: | ||
await self.channel.close() | ||
if self.connection and not self.connection.is_closed: | ||
await self.connection.close() | ||
|
||
self.connection = None | ||
self.channel = None | ||
|
||
async def connect(self, ampq_url: str, publisher_confirms: bool) -> None: | ||
if self._is_open: | ||
return | ||
try: | ||
self.connection = await connect_robust(url=ampq_url) | ||
self.channel = await self.connection.channel(publisher_confirms=publisher_confirms) # type: ignore # noqa: PGH003 | ||
logger.info("Connected to RabbitMQ successfully.") | ||
except Exception: | ||
await self._close() | ||
logger.exception("Failed to connect to RabbitMQ.") | ||
raise | ||
|
||
async def disconnect(self) -> None: | ||
if not self._is_open: | ||
return | ||
await self._close() | ||
|
||
async def publish(self, message: Message | dict, routing_key: str, expiration: int | None = None) -> None: | ||
if not self.channel or self.channel.is_closed: | ||
raise RuntimeError("RabbitMQ channel is not open.") | ||
if not isinstance(message, Message): | ||
message = Message(body=json.dumps(message, default=str).encode(), expiration=expiration) | ||
async with self.channel.transaction(): | ||
await self.channel.default_exchange.publish(message, routing_key) | ||
|
||
|
||
mq = RabbitMQSession() |