Skip to content

Commit 4bf0a03

Browse files
authored
Develop (#17)
# **Merge to Develop** <!-- This PR fixes #NUMBER_OF_THE_ISSUE, and fixes #NUMBER_OF_THE_ISSUE --> ## **Description** <!-- πŸ“›πŸ“› Please include a summary of the change and/or which issue is fixed. List any dependencies required for this change, if there are any. πŸ“›πŸ“› --> * Added Connection Pooling * Fixed Customer example endpoints fields with camel case --- ### **Additional context** <!-- Add any other context or additional information about the pull request.--> * <!-- πŸ“›πŸ“›πŸ“›πŸ“› If it fixes any current issue please let us know this way: Uncomment the comment above "description", then add your number of issues after the "#". Example: # **This pull request fixes #NUMBER_OF_THE_ISSUE issue** If there are multiple issues to be closed with the merge of this pull request please do it like so: **This pull request fixes #NUMBER_OF_THE_ISSUE, fixes #NUMBER_OF_THE_ISSUE and fixes #NUMBER_OF_THE_ISSUE issue**. For more information on closing issues using keywords, please check https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords#closing-multiple-issues πŸ“›πŸ“›πŸ“›πŸ“› -->
2 parents c823e70 + 76b977f commit 4bf0a03

File tree

4 files changed

+77
-54
lines changed

4 files changed

+77
-54
lines changed

β€Ž.gitguardian.ymlβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
incident:
2+
ignore:
3+
- name: "Ignore example secrets"
4+
match: "src/core/config.py"
5+
reason: "Example credentials that are not used in production."

β€Žsrc/controller/api/endpoints/customer.pyβ€Ž

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ async def get_customers(
5252
request: Request,
5353
http_request_info: CommonDeps,
5454
db_connection: Annotated[Session, Depends(get_db_session)],
55+
street: Annotated[str | None, Query(description="Filter customer by street")] = None,
56+
city: Annotated[str | None, Query(description="Filter customer by city")] = None,
57+
country: Annotated[str | None, Query(description="Filter customer by country")] = None,
58+
postalCode: Annotated[str | None, Query(description="Filter customer by postal code")] = None,
5559
limit: Annotated[
5660
int,
5761
Query(
@@ -74,10 +78,6 @@ async def get_customers(
7478
le=100,
7579
),
7680
] = 0,
77-
street: Annotated[str | None, Query(description="Filter customer by street")] = None,
78-
city: Annotated[str | None, Query(description="Filter customer by city")] = None,
79-
country: Annotated[str | None, Query(description="Filter customer by country")] = None,
80-
postalCode: Annotated[str | None, Query(description="Filter customer by postal code")] = None,
8181
) -> JSONResponse:
8282
"""List of customers."""
8383
logger.info("Entering...")
@@ -154,7 +154,7 @@ async def post_customer(
154154

155155

156156
@router.put(
157-
"/v1/customers/{customer_id}",
157+
"/v1/customers/{customerId}",
158158
responses={
159159
204: {"description": "No Content."},
160160
400: {"model": ErrorMessage, "description": "Bad Request."},
@@ -174,29 +174,29 @@ async def post_customer(
174174
response_model_by_alias=True,
175175
)
176176
async def put_customers_customer_id(
177-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
177+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
178178
http_request_info: CommonDeps,
179179
db_connection: Annotated[Session, Depends(get_db_session)],
180180
post_customers_request: Annotated[CustomerUpdate, Body()],
181181
) -> Response:
182182
"""Update of the information of a customer with the matching Id."""
183183
logger.info("Entering...")
184-
logger.debug("Updating customer with id %s", customer_id)
184+
logger.debug("Updating customer with id %s", customerId)
185185
try:
186-
CustomerApplicationService.put_customers(db_connection, customer_id, post_customers_request)
187-
logger.debug("Customer with id %s updated", customer_id)
186+
CustomerApplicationService.put_customers(db_connection, customerId, post_customers_request)
187+
logger.debug("Customer with id %s updated", customerId)
188188
except ElementNotFoundError as error:
189-
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
189+
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
190190
raise HTTP404NotFoundError from error
191191
except Exception as error:
192-
logger.exception("Error updating customer with id %s", customer_id)
192+
logger.exception("Error updating customer with id %s", customerId)
193193
raise HTTP500InternalServerError from error
194194
logger.info("Exiting...")
195195
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)
196196

197197

198198
@router.delete(
199-
"/v1/customers/{customer_id}",
199+
"/v1/customers/{customerId}",
200200
responses={
201201
204: {"description": "No Content."},
202202
400: {"model": ErrorMessage, "description": "Bad Request."},
@@ -215,28 +215,28 @@ async def put_customers_customer_id(
215215
response_model=None,
216216
)
217217
async def delete_customer_id(
218-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
218+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
219219
http_request_info: CommonDeps,
220220
db_connection: Annotated[Session, Depends(get_db_session)],
221221
) -> Response:
222222
"""Delete the information of the customer with the matching Id."""
223223
logger.info("Entering...")
224-
logger.debug("Deleting customer with id %s", customer_id)
224+
logger.debug("Deleting customer with id %s", customerId)
225225
try:
226-
CustomerApplicationService.delete_customer(db_connection, customer_id)
227-
logger.debug("Customer with id %s deleted", customer_id)
226+
CustomerApplicationService.delete_customer(db_connection, customerId)
227+
logger.debug("Customer with id %s deleted", customerId)
228228
except ElementNotFoundError as error:
229-
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
229+
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
230230
raise HTTP404NotFoundError from error
231231
except Exception as error:
232-
logger.exception("Error deleting customer with id %s", customer_id)
232+
logger.exception("Error deleting customer with id %s", customerId)
233233
raise HTTP500InternalServerError from error
234234
logger.info("Exiting...")
235235
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)
236236

237237

238238
@router.get(
239-
"/v1/customers/{customer_id}",
239+
"/v1/customers/{customerId}",
240240
responses={
241241
200: {"model": CustomerDetailResponse, "description": "OK."},
242242
401: {"model": ErrorMessage, "description": "Unauthorized."},
@@ -256,21 +256,21 @@ async def delete_customer_id(
256256
response_model=CustomerDetailResponse,
257257
)
258258
async def get_customer_id(
259+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
259260
http_request_info: CommonDeps,
260261
db_connection: Annotated[Session, Depends(get_db_session)],
261-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
262262
) -> JSONResponse:
263263
"""Retrieve the information of the customer with the matching code."""
264264
logger.info("Entering...")
265-
logger.debug("Getting customer with id %s", customer_id)
265+
logger.debug("Getting customer with id %s", customerId)
266266
try:
267-
api_data = CustomerApplicationService.get_customer_id(db_connection, customer_id)
268-
logger.debug("Customer with id %s retrieved", customer_id)
267+
api_data = CustomerApplicationService.get_customer_id(db_connection, customerId)
268+
logger.debug("Customer with id %s retrieved", customerId)
269269
except ElementNotFoundError as error:
270-
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
270+
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
271271
raise HTTP404NotFoundError from error
272272
except Exception as error:
273-
logger.exception("Error getting customer with id %s", customer_id)
273+
logger.exception("Error getting customer with id %s", customerId)
274274
raise HTTP500InternalServerError from error
275275
logger.info("Exiting...")
276276
return JSONResponse(
@@ -279,7 +279,7 @@ async def get_customer_id(
279279

280280

281281
@router.post(
282-
"/v1/customers/{customer_id}/addresses",
282+
"/v1/customers/{customerId}/addresses",
283283
responses={
284284
201: {"description": "Created."},
285285
400: {"model": ErrorMessage, "description": "Bad Request."},
@@ -297,18 +297,18 @@ async def get_customer_id(
297297
response_model_by_alias=True,
298298
)
299299
async def post_address(
300+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
301+
post_address_request: Annotated[AddressBase, Body()],
300302
request: Request,
301303
http_request_info: CommonDeps,
302-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
303304
db_connection: Annotated[Session, Depends(get_db_session)],
304-
post_address_request: Annotated[AddressBase, Body()],
305305
) -> Response:
306306
"""Add a new address into the list."""
307307
logger.info("Entering...")
308308
try:
309309
address_id = CustomerApplicationService.post_address(
310310
db_connection,
311-
customer_id,
311+
customerId,
312312
post_address_request,
313313
)
314314
logger.debug("Address created")
@@ -317,14 +317,14 @@ async def post_address(
317317
raise HTTP500InternalServerError from error
318318
url = request.url
319319
headers = http_request_info | {
320-
"location": f"{url.scheme}://{url.netloc}/customers/{customer_id}/addresses/{address_id}",
320+
"location": f"{url.scheme}://{url.netloc}/customers/{customerId}/addresses/{address_id}",
321321
}
322322
logger.info("Exiting...")
323323
return Response(status_code=status.HTTP_201_CREATED, headers=headers)
324324

325325

326326
@router.put(
327-
"/v1/customers/{customer_id}/addresses/{address_id}",
327+
"/v1/customers/{customerId}/addresses/{addressId}",
328328
responses={
329329
204: {"description": "No Content."},
330330
400: {"model": ErrorMessage, "description": "Bad Request."},
@@ -344,35 +344,35 @@ async def post_address(
344344
response_model_by_alias=True,
345345
)
346346
async def put_addresses_customer_id(
347-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
348-
address_id: Annotated[UUID4, Path(description="Id of a specific address.")],
347+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
348+
addressId: Annotated[UUID4, Path(description="Id of a specific address.")],
349349
http_request_info: CommonDeps,
350350
db_connection: Annotated[Session, Depends(get_db_session)],
351351
post_address_request: Annotated[AddressBase, Body()],
352352
) -> Response:
353353
"""Update of the information of a customer with the matching Id."""
354354
logger.info("Entering...")
355-
logger.debug("Updating address with id %s", address_id)
355+
logger.debug("Updating address with id %s", addressId)
356356
try:
357357
CustomerApplicationService.put_address(
358358
db_connection,
359-
customer_id,
360-
address_id,
359+
customerId,
360+
addressId,
361361
post_address_request,
362362
)
363-
logger.debug("Address with id %s updated", address_id)
363+
logger.debug("Address with id %s updated", addressId)
364364
except ElementNotFoundError as error:
365-
logger.error("Address with id %s not found", address_id) # noqa: TRY400
365+
logger.error("Address with id %s not found", addressId) # noqa: TRY400
366366
raise HTTP404NotFoundError from error
367367
except Exception as error:
368-
logger.exception("Error updating address with id %s", address_id)
368+
logger.exception("Error updating address with id %s", addressId)
369369
raise HTTP500InternalServerError from error
370370
logger.info("Exiting...")
371371
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)
372372

373373

374374
@router.delete(
375-
"/v1/customers/{customer_id}/addresses/{address_id}",
375+
"/v1/customers/{customerId}/addresses/{addressId}",
376376
responses={
377377
204: {"description": "No Content."},
378378
400: {"model": ErrorMessage, "description": "Bad Request."},
@@ -391,19 +391,19 @@ async def put_addresses_customer_id(
391391
response_model=None,
392392
)
393393
async def delete_address_id(
394-
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
395-
address_id: Annotated[UUID4, Path(description="Id of a specific address.")],
394+
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
395+
addressId: Annotated[UUID4, Path(description="Id of a specific address.")],
396396
http_request_info: CommonDeps,
397397
db_connection: Annotated[Session, Depends(get_db_session)],
398398
) -> Response:
399399
"""Delete the information of the customer with the matching Id."""
400400
logger.info("Entering...")
401-
logger.debug("Deleting address with id %s", address_id)
401+
logger.debug("Deleting address with id %s", addressId)
402402
try:
403-
CustomerApplicationService.delete_address(db_connection, customer_id, address_id)
404-
logger.debug("Address with id %s deleted", address_id)
403+
CustomerApplicationService.delete_address(db_connection, customerId, addressId)
404+
logger.debug("Address with id %s deleted", addressId)
405405
except Exception as error:
406-
logger.exception("Error deleting address with id %s", address_id)
406+
logger.exception("Error deleting address with id %s", addressId)
407407
raise HTTP500InternalServerError from error
408408
logger.info("Exiting...")
409409
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)

β€Žsrc/core/config.pyβ€Ž

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ class Settings(BaseSettings):
3535
"""Represents the configuration settings for the application."""
3636

3737
# CORE SETTINGS
38-
## Could be improved by using a secret manager like AWS Secrets Manager or Hashicorp Vault
3938
SECRET_KEY: str = "HDx09iYK97MzUqezQ8InThpcEBk791oi"
4039
ENVIRONMENT: Literal["DEV", "PYTEST", "PREPROD", "PROD"] = "DEV"
4140
## BACKEND_CORS_ORIGINS and ALLOWED_HOSTS are a JSON-formatted list of origins
@@ -45,13 +44,23 @@ class Settings(BaseSettings):
4544
APP_LOG_FILE_PATH: str = "logs/app.log"
4645

4746
# POSTGRESQL DATABASE
48-
POSTGRES_SERVER: str = "db"
49-
POSTGRES_USER: str = "postgres"
50-
POSTGRES_PASSWORD: str = "postgres"
51-
POSTGRES_PORT: int = 5432
52-
POSTGRES_DB: str = "app-db"
47+
POSTGRES_SERVER: str = "db" # The name of the service in the docker-compose file
48+
POSTGRES_USER: str = "postgres" # The default username for the PostgreSQL database
49+
POSTGRES_PASSWORD: str = "postgres" # The default password for the PostgreSQL database
50+
POSTGRES_PORT: int = 5432 # The default port for the PostgreSQL database
51+
POSTGRES_DB: str = "app-db" # The default database name for the PostgreSQL database
5352
SQLALCHEMY_DATABASE_URI: PostgresDsn | None = None
5453

54+
# CONNECTION POOL SETTINGS
55+
# The size of the pool to be maintained, defaults to 5
56+
POOL_SIZE: int = 10
57+
# Controls the number of connections that can be created after the pool reached its size
58+
MAX_OVERFLOW: int = 20
59+
# Number of seconds to wait before giving up on getting a connection from the pool
60+
POOL_TIMEOUT: int = 30
61+
# Number of seconds after which a connection is recycled (preventing stale connections)
62+
POOL_RECYCLE: int = 1800
63+
5564
@field_validator("SQLALCHEMY_DATABASE_URI", mode="before")
5665
@classmethod
5766
def assemble_db_connection(

β€Žsrc/repository/session.pyβ€Ž

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@
55

66
from sqlalchemy import create_engine
77
from sqlalchemy.orm import Session, sessionmaker
8+
from sqlalchemy.pool import QueuePool
89

910
from src.core.config import settings
1011

1112
logger = logging.getLogger(__name__)
1213

13-
# Create a new engine
14-
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI), pool_pre_ping=True)
14+
# Create a new engine with connection pooling
15+
engine = create_engine(
16+
str(settings.SQLALCHEMY_DATABASE_URI),
17+
pool_pre_ping=True,
18+
poolclass=QueuePool,
19+
pool_size=settings.POOL_SIZE,
20+
max_overflow=settings.MAX_OVERFLOW,
21+
pool_timeout=settings.POOL_TIMEOUT,
22+
pool_recycle=settings.POOL_RECYCLE,
23+
)
1524

1625
# Create a session factory
1726
session_local = sessionmaker(autocommit=False, autoflush=False, bind=engine)

0 commit comments

Comments
Β (0)