Skip to content

Commit 9387ecb

Browse files
authored
Merge pull request #28 from igorbenav/minor-changes
Minor changes
2 parents 4674656 + 541b203 commit 9387ecb

File tree

5 files changed

+168
-163
lines changed

5 files changed

+168
-163
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
- [x] FastAPI docs behind authentication and hidden based on the environment
6969

7070
#### Structure
71-
- [ ] Remove python-decouple in favor of starlette.config
71+
- [x] Remove python-decouple in favor of starlette.config
7272

7373
#### Tests
7474
- [ ] Add Ruff linter
@@ -424,15 +424,16 @@ First, you may want to take a look at the project structure and understand what
424424
│ │ ├── exceptions.py # Contains core custom exceptions for the application.
425425
│ │ ├── models.py # Base models for the application.
426426
│ │ ├── queue.py # Utilities related to task queues.
427-
│ │ └── security.py # Security utilities like password hashing and token generation.
427+
│ │ ├── security.py # Security utilities like password hashing and token generation.
428+
│ │ └── setup.py # File defining settings and FastAPI application instance definition.
428429
│ │
429430
│ ├── crud # CRUD operations for the application.
430431
│ │ ├── __init__.py
431432
│ │ ├── crud_base.py # Base CRUD operations class that can be extended by other CRUD modules.
432433
│ │ ├── crud_posts.py # CRUD operations for posts.
433434
│ │ └── crud_users.py # CRUD operations for users.
434435
│ │
435-
│ ├── main.py # Entry point for the FastAPI application.
436+
│ ├── main.py # Entry point that imports and creates the FastAPI application instance.
436437
│ ├── models # ORM models for the application.
437438
│ │ ├── __init__.py
438439
│ │ ├── post.py # ORM model for posts.

src/app/core/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from enum import Enum
22

3-
from decouple import config
3+
#from decouple import config
4+
from starlette.config import Config
45
from pydantic_settings import BaseSettings
56

7+
config = Config(".env")
8+
69
class AppSettings(BaseSettings):
710
APP_NAME: str = config("APP_NAME", default="FastAPI app")
811
APP_DESCRIPTION: str | None = config("APP_DESCRIPTION", default=None)

src/app/core/setup.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
from fastapi import FastAPI, APIRouter, Depends
2+
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
3+
from fastapi.openapi.utils import get_openapi
4+
import redis.asyncio as redis
5+
from arq import create_pool
6+
from arq.connections import RedisSettings
7+
import anyio
8+
9+
10+
from app.api.dependencies import get_current_superuser
11+
from app.core import cache, queue
12+
from app.core.config import settings
13+
from app.core.database import Base
14+
from app.core.database import async_engine as engine
15+
from app.core.config import (
16+
DatabaseSettings,
17+
RedisCacheSettings,
18+
AppSettings,
19+
ClientSideCacheSettings,
20+
RedisQueueSettings,
21+
EnvironmentOption,
22+
EnvironmentSettings
23+
)
24+
25+
# -------------- database --------------
26+
async def create_tables():
27+
async with engine.begin() as conn:
28+
await conn.run_sync(Base.metadata.create_all)
29+
30+
31+
# -------------- cache --------------
32+
async def create_redis_cache_pool():
33+
cache.pool = redis.ConnectionPool.from_url(settings.REDIS_CACHE_URL)
34+
cache.client = redis.Redis.from_pool(cache.pool)
35+
36+
37+
async def close_redis_cache_pool():
38+
await cache.client.aclose()
39+
40+
41+
# -------------- queue --------------
42+
async def create_redis_queue_pool():
43+
queue.pool = await create_pool(
44+
RedisSettings(host=settings.REDIS_QUEUE_HOST, port=settings.REDIS_QUEUE_PORT)
45+
)
46+
47+
48+
async def close_redis_queue_pool():
49+
await queue.pool.aclose()
50+
51+
52+
# -------------- application --------------
53+
async def set_threadpool_tokens(number_of_tokens=100):
54+
limiter = anyio.to_thread.current_default_thread_limiter()
55+
limiter.total_tokens = number_of_tokens
56+
57+
58+
# -------------- application --------------
59+
def create_application(router: APIRouter, settings, **kwargs) -> FastAPI:
60+
"""
61+
Creates and configures a FastAPI application based on the provided settings.
62+
63+
This function initializes a FastAPI application, then conditionally configures
64+
it with various settings and handlers. The specific configuration is determined
65+
by the type of the `settings` object provided.
66+
67+
Parameters
68+
----------
69+
router : APIRouter
70+
The APIRouter object that contains the routes to be included in the FastAPI application.
71+
72+
settings
73+
An instance representing the settings for configuring the FastAPI application. It determines the configuration applied:
74+
75+
- AppSettings: Configures basic app metadata like name, description, contact, and license info.
76+
- DatabaseSettings: Adds event handlers for initializing database tables during startup.
77+
- RedisCacheSettings: Sets up event handlers for creating and closing a Redis cache pool.
78+
- ClientSideCacheSettings: Integrates middleware for client-side caching.
79+
- RedisQueueSettings: Sets up event handlers for creating and closing a Redis queue pool.
80+
- EnvironmentSettings: Conditionally sets documentation URLs and integrates custom routes for API documentation based on environment type.
81+
82+
**kwargs
83+
Extra keyword arguments passed directly to the FastAPI constructor.
84+
85+
Returns
86+
-------
87+
FastAPI
88+
A fully configured FastAPI application instance.
89+
90+
"""
91+
92+
# --- before creating application ---
93+
if isinstance(settings, AppSettings):
94+
to_update = {
95+
"title": settings.APP_NAME,
96+
"description": settings.APP_DESCRIPTION,
97+
"contact": {
98+
"name": settings.CONTACT_NAME,
99+
"email": settings.CONTACT_EMAIL
100+
},
101+
"license_info": {
102+
"name": settings.LICENSE_NAME
103+
}
104+
}
105+
kwargs.update(to_update)
106+
107+
if isinstance(settings, EnvironmentSettings):
108+
kwargs.update(
109+
{
110+
"docs_url": None,
111+
"redoc_url": None,
112+
"openapi_url": None
113+
}
114+
)
115+
116+
application = FastAPI(**kwargs)
117+
118+
# --- application created ---
119+
application.include_router(router)
120+
application.add_event_handler("startup", set_threadpool_tokens)
121+
122+
if isinstance(settings, DatabaseSettings):
123+
application.add_event_handler("startup", create_tables)
124+
125+
if isinstance(settings, RedisCacheSettings):
126+
application.add_event_handler("startup", create_redis_cache_pool)
127+
application.add_event_handler("shutdown", close_redis_cache_pool)
128+
129+
if isinstance(settings, ClientSideCacheSettings):
130+
application.add_middleware(cache.ClientCacheMiddleware, max_age=60)
131+
132+
if isinstance(settings, RedisQueueSettings):
133+
application.add_event_handler("startup", create_redis_queue_pool)
134+
application.add_event_handler("shutdown", close_redis_queue_pool)
135+
136+
if isinstance(settings, EnvironmentSettings):
137+
if settings.ENVIRONMENT != EnvironmentOption.PRODUCTION:
138+
docs_router = APIRouter()
139+
if settings.ENVIRONMENT != EnvironmentOption.LOCAL:
140+
docs_router = APIRouter(dependencies=[Depends(get_current_superuser)])
141+
142+
@docs_router.get("/docs", include_in_schema=False)
143+
async def get_swagger_documentation():
144+
return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")
145+
146+
147+
@docs_router.get("/redoc", include_in_schema=False)
148+
async def get_redoc_documentation():
149+
return get_redoc_html(openapi_url="/openapi.json", title="docs")
150+
151+
152+
@docs_router.get("/openapi.json", include_in_schema=False)
153+
async def openapi():
154+
return get_openapi(title=application.title, version=application.version, routes=application.routes)
155+
156+
application.include_router(docs_router)
157+
158+
return application

src/app/main.py

Lines changed: 2 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,5 @@
1-
from fastapi import FastAPI, APIRouter, Depends
2-
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
3-
from fastapi.openapi.utils import get_openapi
4-
import redis.asyncio as redis
5-
from arq import create_pool
6-
from arq.connections import RedisSettings
7-
import anyio
8-
1+
from app.core.config import settings
92
from app.api import router
10-
from app.api.dependencies import get_current_superuser
11-
from app.core import cache, queue
12-
from app.core.database import Base
13-
from app.core.database import async_engine as engine
14-
from app.core.config import (
15-
settings,
16-
DatabaseSettings,
17-
RedisCacheSettings,
18-
AppSettings,
19-
ClientSideCacheSettings,
20-
RedisQueueSettings,
21-
EnvironmentOption,
22-
EnvironmentSettings
23-
)
24-
25-
# -------------- database --------------
26-
async def create_tables():
27-
async with engine.begin() as conn:
28-
await conn.run_sync(Base.metadata.create_all)
29-
30-
31-
# -------------- cache --------------
32-
async def create_redis_cache_pool():
33-
cache.pool = redis.ConnectionPool.from_url(settings.REDIS_CACHE_URL)
34-
cache.client = redis.Redis.from_pool(cache.pool)
35-
36-
37-
async def close_redis_cache_pool():
38-
await cache.client.aclose()
39-
40-
41-
# -------------- queue --------------
42-
async def create_redis_queue_pool():
43-
queue.pool = await create_pool(
44-
RedisSettings(host=settings.REDIS_QUEUE_HOST, port=settings.REDIS_QUEUE_PORT)
45-
)
46-
47-
48-
async def close_redis_queue_pool():
49-
await queue.pool.aclose()
50-
51-
52-
# -------------- application --------------
53-
async def set_threadpool_tokens(number_of_tokens=100):
54-
limiter = anyio.to_thread.current_default_thread_limiter()
55-
limiter.total_tokens = number_of_tokens
56-
57-
58-
# -------------- application --------------
59-
def create_application(router: APIRouter, settings, **kwargs) -> FastAPI:
60-
"""
61-
Creates and configures a FastAPI application based on the provided settings.
62-
63-
This function initializes a FastAPI application, then conditionally configures
64-
it with various settings and handlers. The specific configuration is determined
65-
by the type of the `settings` object provided.
66-
67-
Parameters
68-
----------
69-
router : APIRouter
70-
The APIRouter object that contains the routes to be included in the FastAPI application.
71-
72-
settings
73-
An instance representing the settings for configuring the FastAPI application. It determines the configuration applied:
74-
75-
- AppSettings: Configures basic app metadata like name, description, contact, and license info.
76-
- DatabaseSettings: Adds event handlers for initializing database tables during startup.
77-
- RedisCacheSettings: Sets up event handlers for creating and closing a Redis cache pool.
78-
- ClientSideCacheSettings: Integrates middleware for client-side caching.
79-
- RedisQueueSettings: Sets up event handlers for creating and closing a Redis queue pool.
80-
- EnvironmentSettings: Conditionally sets documentation URLs and integrates custom routes for API documentation based on environment type.
81-
82-
**kwargs
83-
Extra keyword arguments passed directly to the FastAPI constructor.
84-
85-
Returns
86-
-------
87-
FastAPI
88-
A fully configured FastAPI application instance.
89-
90-
"""
91-
92-
# --- before creating application ---
93-
if isinstance(settings, AppSettings):
94-
to_update = {
95-
"title": settings.APP_NAME,
96-
"description": settings.APP_DESCRIPTION,
97-
"contact": {
98-
"name": settings.CONTACT_NAME,
99-
"email": settings.CONTACT_EMAIL
100-
},
101-
"license_info": {
102-
"name": settings.LICENSE_NAME
103-
}
104-
}
105-
kwargs.update(to_update)
106-
107-
if isinstance(settings, EnvironmentSettings):
108-
kwargs.update(
109-
{
110-
"docs_url": None,
111-
"redoc_url": None,
112-
"openapi_url": None
113-
}
114-
)
115-
116-
application = FastAPI(**kwargs)
117-
118-
# --- application created ---
119-
application.include_router(router)
120-
application.add_event_handler("startup", set_threadpool_tokens)
121-
122-
if isinstance(settings, DatabaseSettings):
123-
application.add_event_handler("startup", create_tables)
124-
125-
if isinstance(settings, RedisCacheSettings):
126-
application.add_event_handler("startup", create_redis_cache_pool)
127-
application.add_event_handler("shutdown", close_redis_cache_pool)
128-
129-
if isinstance(settings, ClientSideCacheSettings):
130-
application.add_middleware(cache.ClientCacheMiddleware, max_age=60)
131-
132-
if isinstance(settings, RedisQueueSettings):
133-
application.add_event_handler("startup", create_redis_queue_pool)
134-
application.add_event_handler("shutdown", close_redis_queue_pool)
135-
136-
if isinstance(settings, EnvironmentSettings):
137-
if settings.ENVIRONMENT != EnvironmentOption.PRODUCTION:
138-
docs_router = APIRouter()
139-
if settings.ENVIRONMENT != EnvironmentOption.LOCAL:
140-
docs_router = APIRouter(dependencies=[Depends(get_current_superuser)])
141-
142-
@docs_router.get("/docs", include_in_schema=False)
143-
async def get_swagger_documentation():
144-
return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")
145-
146-
147-
@docs_router.get("/redoc", include_in_schema=False)
148-
async def get_redoc_documentation():
149-
return get_redoc_html(openapi_url="/openapi.json", title="docs")
150-
151-
152-
@docs_router.get("/openapi.json", include_in_schema=False)
153-
async def openapi():
154-
return get_openapi(title=application.title, version=application.version, routes=application.routes)
155-
156-
application.include_router(docs_router)
157-
158-
return application
159-
3+
from app.core.setup import create_application
1604

1615
app = create_application(router=router, settings=settings)

src/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ passlib = "^1.7.4"
2424
SQLAlchemy = "^2.0.21"
2525
pytest = "^7.4.2"
2626
python-multipart = "^0.0.6"
27-
python-decouple = "^3.8"
2827
greenlet = "^2.0.2"
2928
httpx = "^0.25.0"
3029
pydantic-settings = "^2.0.3"

0 commit comments

Comments
 (0)