Skip to content

Commit

Permalink
Develop (#17)
Browse files Browse the repository at this point in the history
# **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
πŸ“›πŸ“›πŸ“›πŸ“› -->
  • Loading branch information
n0nuser authored Jun 5, 2024
2 parents c823e70 + 76b977f commit 4bf0a03
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .gitguardian.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
incident:
ignore:
- name: "Ignore example secrets"
match: "src/core/config.py"
reason: "Example credentials that are not used in production."
92 changes: 46 additions & 46 deletions src/controller/api/endpoints/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ async def get_customers(
request: Request,
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
street: Annotated[str | None, Query(description="Filter customer by street")] = None,
city: Annotated[str | None, Query(description="Filter customer by city")] = None,
country: Annotated[str | None, Query(description="Filter customer by country")] = None,
postalCode: Annotated[str | None, Query(description="Filter customer by postal code")] = None,
limit: Annotated[
int,
Query(
Expand All @@ -74,10 +78,6 @@ async def get_customers(
le=100,
),
] = 0,
street: Annotated[str | None, Query(description="Filter customer by street")] = None,
city: Annotated[str | None, Query(description="Filter customer by city")] = None,
country: Annotated[str | None, Query(description="Filter customer by country")] = None,
postalCode: Annotated[str | None, Query(description="Filter customer by postal code")] = None,
) -> JSONResponse:
"""List of customers."""
logger.info("Entering...")
Expand Down Expand Up @@ -154,7 +154,7 @@ async def post_customer(


@router.put(
"/v1/customers/{customer_id}",
"/v1/customers/{customerId}",
responses={
204: {"description": "No Content."},
400: {"model": ErrorMessage, "description": "Bad Request."},
Expand All @@ -174,29 +174,29 @@ async def post_customer(
response_model_by_alias=True,
)
async def put_customers_customer_id(
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
post_customers_request: Annotated[CustomerUpdate, Body()],
) -> Response:
"""Update of the information of a customer with the matching Id."""
logger.info("Entering...")
logger.debug("Updating customer with id %s", customer_id)
logger.debug("Updating customer with id %s", customerId)
try:
CustomerApplicationService.put_customers(db_connection, customer_id, post_customers_request)
logger.debug("Customer with id %s updated", customer_id)
CustomerApplicationService.put_customers(db_connection, customerId, post_customers_request)
logger.debug("Customer with id %s updated", customerId)
except ElementNotFoundError as error:
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
raise HTTP404NotFoundError from error
except Exception as error:
logger.exception("Error updating customer with id %s", customer_id)
logger.exception("Error updating customer with id %s", customerId)
raise HTTP500InternalServerError from error
logger.info("Exiting...")
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)


@router.delete(
"/v1/customers/{customer_id}",
"/v1/customers/{customerId}",
responses={
204: {"description": "No Content."},
400: {"model": ErrorMessage, "description": "Bad Request."},
Expand All @@ -215,28 +215,28 @@ async def put_customers_customer_id(
response_model=None,
)
async def delete_customer_id(
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
) -> Response:
"""Delete the information of the customer with the matching Id."""
logger.info("Entering...")
logger.debug("Deleting customer with id %s", customer_id)
logger.debug("Deleting customer with id %s", customerId)
try:
CustomerApplicationService.delete_customer(db_connection, customer_id)
logger.debug("Customer with id %s deleted", customer_id)
CustomerApplicationService.delete_customer(db_connection, customerId)
logger.debug("Customer with id %s deleted", customerId)
except ElementNotFoundError as error:
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
raise HTTP404NotFoundError from error
except Exception as error:
logger.exception("Error deleting customer with id %s", customer_id)
logger.exception("Error deleting customer with id %s", customerId)
raise HTTP500InternalServerError from error
logger.info("Exiting...")
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)


@router.get(
"/v1/customers/{customer_id}",
"/v1/customers/{customerId}",
responses={
200: {"model": CustomerDetailResponse, "description": "OK."},
401: {"model": ErrorMessage, "description": "Unauthorized."},
Expand All @@ -256,21 +256,21 @@ async def delete_customer_id(
response_model=CustomerDetailResponse,
)
async def get_customer_id(
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
) -> JSONResponse:
"""Retrieve the information of the customer with the matching code."""
logger.info("Entering...")
logger.debug("Getting customer with id %s", customer_id)
logger.debug("Getting customer with id %s", customerId)
try:
api_data = CustomerApplicationService.get_customer_id(db_connection, customer_id)
logger.debug("Customer with id %s retrieved", customer_id)
api_data = CustomerApplicationService.get_customer_id(db_connection, customerId)
logger.debug("Customer with id %s retrieved", customerId)
except ElementNotFoundError as error:
logger.error("Customer with id %s not found", customer_id) # noqa: TRY400
logger.error("Customer with id %s not found", customerId) # noqa: TRY400
raise HTTP404NotFoundError from error
except Exception as error:
logger.exception("Error getting customer with id %s", customer_id)
logger.exception("Error getting customer with id %s", customerId)
raise HTTP500InternalServerError from error
logger.info("Exiting...")
return JSONResponse(
Expand All @@ -279,7 +279,7 @@ async def get_customer_id(


@router.post(
"/v1/customers/{customer_id}/addresses",
"/v1/customers/{customerId}/addresses",
responses={
201: {"description": "Created."},
400: {"model": ErrorMessage, "description": "Bad Request."},
Expand All @@ -297,18 +297,18 @@ async def get_customer_id(
response_model_by_alias=True,
)
async def post_address(
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
post_address_request: Annotated[AddressBase, Body()],
request: Request,
http_request_info: CommonDeps,
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
db_connection: Annotated[Session, Depends(get_db_session)],
post_address_request: Annotated[AddressBase, Body()],
) -> Response:
"""Add a new address into the list."""
logger.info("Entering...")
try:
address_id = CustomerApplicationService.post_address(
db_connection,
customer_id,
customerId,
post_address_request,
)
logger.debug("Address created")
Expand All @@ -317,14 +317,14 @@ async def post_address(
raise HTTP500InternalServerError from error
url = request.url
headers = http_request_info | {
"location": f"{url.scheme}://{url.netloc}/customers/{customer_id}/addresses/{address_id}",
"location": f"{url.scheme}://{url.netloc}/customers/{customerId}/addresses/{address_id}",
}
logger.info("Exiting...")
return Response(status_code=status.HTTP_201_CREATED, headers=headers)


@router.put(
"/v1/customers/{customer_id}/addresses/{address_id}",
"/v1/customers/{customerId}/addresses/{addressId}",
responses={
204: {"description": "No Content."},
400: {"model": ErrorMessage, "description": "Bad Request."},
Expand All @@ -344,35 +344,35 @@ async def post_address(
response_model_by_alias=True,
)
async def put_addresses_customer_id(
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
address_id: Annotated[UUID4, Path(description="Id of a specific address.")],
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
addressId: Annotated[UUID4, Path(description="Id of a specific address.")],
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
post_address_request: Annotated[AddressBase, Body()],
) -> Response:
"""Update of the information of a customer with the matching Id."""
logger.info("Entering...")
logger.debug("Updating address with id %s", address_id)
logger.debug("Updating address with id %s", addressId)
try:
CustomerApplicationService.put_address(
db_connection,
customer_id,
address_id,
customerId,
addressId,
post_address_request,
)
logger.debug("Address with id %s updated", address_id)
logger.debug("Address with id %s updated", addressId)
except ElementNotFoundError as error:
logger.error("Address with id %s not found", address_id) # noqa: TRY400
logger.error("Address with id %s not found", addressId) # noqa: TRY400
raise HTTP404NotFoundError from error
except Exception as error:
logger.exception("Error updating address with id %s", address_id)
logger.exception("Error updating address with id %s", addressId)
raise HTTP500InternalServerError from error
logger.info("Exiting...")
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)


@router.delete(
"/v1/customers/{customer_id}/addresses/{address_id}",
"/v1/customers/{customerId}/addresses/{addressId}",
responses={
204: {"description": "No Content."},
400: {"model": ErrorMessage, "description": "Bad Request."},
Expand All @@ -391,19 +391,19 @@ async def put_addresses_customer_id(
response_model=None,
)
async def delete_address_id(
customer_id: Annotated[UUID4, Path(description="Id of a specific customer.")],
address_id: Annotated[UUID4, Path(description="Id of a specific address.")],
customerId: Annotated[UUID4, Path(description="Id of a specific customer.")],
addressId: Annotated[UUID4, Path(description="Id of a specific address.")],
http_request_info: CommonDeps,
db_connection: Annotated[Session, Depends(get_db_session)],
) -> Response:
"""Delete the information of the customer with the matching Id."""
logger.info("Entering...")
logger.debug("Deleting address with id %s", address_id)
logger.debug("Deleting address with id %s", addressId)
try:
CustomerApplicationService.delete_address(db_connection, customer_id, address_id)
logger.debug("Address with id %s deleted", address_id)
CustomerApplicationService.delete_address(db_connection, customerId, addressId)
logger.debug("Address with id %s deleted", addressId)
except Exception as error:
logger.exception("Error deleting address with id %s", address_id)
logger.exception("Error deleting address with id %s", addressId)
raise HTTP500InternalServerError from error
logger.info("Exiting...")
return Response(status_code=status.HTTP_204_NO_CONTENT, headers=http_request_info)
21 changes: 15 additions & 6 deletions src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class Settings(BaseSettings):
"""Represents the configuration settings for the application."""

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

# POSTGRESQL DATABASE
POSTGRES_SERVER: str = "db"
POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str = "postgres"
POSTGRES_PORT: int = 5432
POSTGRES_DB: str = "app-db"
POSTGRES_SERVER: str = "db" # The name of the service in the docker-compose file
POSTGRES_USER: str = "postgres" # The default username for the PostgreSQL database
POSTGRES_PASSWORD: str = "postgres" # The default password for the PostgreSQL database
POSTGRES_PORT: int = 5432 # The default port for the PostgreSQL database
POSTGRES_DB: str = "app-db" # The default database name for the PostgreSQL database
SQLALCHEMY_DATABASE_URI: PostgresDsn | None = None

# CONNECTION POOL SETTINGS
# The size of the pool to be maintained, defaults to 5
POOL_SIZE: int = 10
# Controls the number of connections that can be created after the pool reached its size
MAX_OVERFLOW: int = 20
# Number of seconds to wait before giving up on getting a connection from the pool
POOL_TIMEOUT: int = 30
# Number of seconds after which a connection is recycled (preventing stale connections)
POOL_RECYCLE: int = 1800

@field_validator("SQLALCHEMY_DATABASE_URI", mode="before")
@classmethod
def assemble_db_connection(
Expand Down
13 changes: 11 additions & 2 deletions src/repository/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@

from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import QueuePool

from src.core.config import settings

logger = logging.getLogger(__name__)

# Create a new engine
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI), pool_pre_ping=True)
# Create a new engine with connection pooling
engine = create_engine(
str(settings.SQLALCHEMY_DATABASE_URI),
pool_pre_ping=True,
poolclass=QueuePool,
pool_size=settings.POOL_SIZE,
max_overflow=settings.MAX_OVERFLOW,
pool_timeout=settings.POOL_TIMEOUT,
pool_recycle=settings.POOL_RECYCLE,
)

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

0 comments on commit 4bf0a03

Please sign in to comment.