-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding CreateClientFunction #50
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
@@ -0,0 +1,61 @@ | ||
name: clients_pipeline | ||
|
||
on: | ||
push: | ||
paths: | ||
- 'src/clients/**' | ||
branches: | ||
- 'main' | ||
- 'feature**' | ||
workflow_dispatch: | ||
|
||
jobs: | ||
run_unit_tests: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python 3.11 | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.11' | ||
- name: Install dependencies | ||
run: | | ||
make install | ||
working-directory: ./tests | ||
- name: Run tests | ||
run: | | ||
make test | ||
working-directory: ./tests | ||
|
||
development: | ||
if: startsWith(github.event.ref, 'refs/heads/main') | ||
uses: ./.github/workflows/pipeline_template.yaml | ||
needs: [run_unit_tests] | ||
with: | ||
sam_deploy_overrides: "StageName=development" | ||
stack_name: ${{ vars.CLIENTS_STACK_NAME }}-development | ||
sam_template: src/clients/template.yaml | ||
aws_region: us-east-1 | ||
pipeline_execution_role: ${{ vars.PIPELINE_EXECUTION_ROLE_DEV }} | ||
cloudformation_execution_role: ${{ vars.CLOUDFORMATION_EXECUTION_ROLE_DEV }} | ||
artifacts_bucket: ${{ vars.ARTIFACTS_BUCKET_DEV }} | ||
stage_name: development | ||
secrets: | ||
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID_MBU }} | ||
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY_MBU }} | ||
uat: | ||
if: startsWith(github.event.ref, 'refs/heads/main') | ||
uses: ./.github/workflows/pipeline_template.yaml | ||
needs: [development] | ||
with: | ||
sam_deploy_overrides: "StageName=uat LogLevel=DEBUG" | ||
stack_name: ${{ vars.CLIENTS_STACK_NAME }}-uat | ||
sam_template: src/clients/template.yaml | ||
aws_region: us-east-1 | ||
pipeline_execution_role: ${{ vars.PIPELINE_EXECUTION_ROLE_UAT }} | ||
cloudformation_execution_role: ${{ vars.CLOUDFORMATION_EXECUTION_ROLE_UAT }} | ||
artifacts_bucket: ${{ vars.ARTIFACTS_BUCKET_UAT }} | ||
stage_name: uat | ||
secrets: | ||
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID_HUAT }} | ||
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY_HUAT }} |
Empty file.
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,30 @@ | ||
# Own's modules | ||
from client_modules.data_access.dynamo_handler import DynamoDBHandler | ||
import settings | ||
|
||
|
||
class ClientDAO: | ||
""" | ||
A class for handling interactions with the DynamoDB table and the Lambda Function. | ||
""" | ||
|
||
def __init__(self): | ||
""" | ||
Initializes a new instance of the DAO class. | ||
""" | ||
self.clients_db = DynamoDBHandler( | ||
table_name=settings.CLIENTS_TABLE_NAME, | ||
partition_key="Id", | ||
) | ||
|
||
def create_client(self, item: dict) -> dict: | ||
""" | ||
Attempts to insert a new record for a client into the DynamoDB table. | ||
|
||
:param item: Client representation | ||
:type item: dict | ||
:return: a dictionary that contains the response object | ||
:rtype: dict | ||
""" | ||
response = self.clients_db.insert_record(item) | ||
return response |
Empty file.
245 changes: 245 additions & 0 deletions
245
src/clients/client_modules/data_access/dynamo_handler.py
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,245 @@ | ||
# Python libraries | ||
import json | ||
from decimal import Decimal | ||
from typing import Dict | ||
from typing import Any | ||
|
||
# Own modules | ||
from client_modules.utils.aws import AWSClientManager | ||
|
||
# Third-party libraries | ||
from aws_lambda_powertools import Logger | ||
from botocore.exceptions import ClientError | ||
|
||
|
||
class DynamoDBHandler: | ||
""" | ||
A class for handling interactions with a DynamoDB table | ||
""" | ||
|
||
HTTP_STATUS_OK = 200 | ||
HTTP_STATUS_CREATED = 201 | ||
HTTP_STATUS_NO_CONTENT = 204 | ||
HTTP_STATUS_BAD_REQUEST = 400 | ||
HTTP_STATUS_FORBIDDEN = 403 | ||
HTTP_STATUS_NOT_FOUND = 404 | ||
HTTP_STATUS_INTERNAL_SERVER_ERROR = 500 | ||
|
||
def __init__(self, table_name: str, partition_key: str, sort_key: str = None): | ||
self.table_name = table_name | ||
self.partition_key = partition_key | ||
self.sort_key = sort_key | ||
aws_resources_manager = AWSClientManager() | ||
dynamodb_resource = aws_resources_manager.dynamodb | ||
self.table = dynamodb_resource.Table(table_name) | ||
self.logger = Logger() | ||
|
||
def insert_record(self, item: dict) -> Dict[str, Any]: | ||
"""This function is used to save a record to a database. | ||
It takes in a dictionary, which is build from a Client Model, as an argument and attempts to put the item into the database. | ||
If the response from the database is successful, it returns a status of "success". | ||
If not, it returns a status of "error" along with the HTTP status code and details about the error message. | ||
If there is an AWS ClientError, it logs information about the error and also returns a status of "error" along | ||
with the HTTP status code and details about the error message. | ||
|
||
:param item: client representation built as a dict | ||
:type item: dict | ||
:return: A summary of the put_item action | ||
:rtype: Dict[str, Any] | ||
""" | ||
try: | ||
db_item = json.loads(json.dumps(item), parse_float=Decimal) | ||
response = self.table.put_item(Item=db_item) | ||
if response["ResponseMetadata"]["HTTPStatusCode"] == self.HTTP_STATUS_OK: | ||
self.logger.info("Client was created in DynamoDB") | ||
return self.build_response_object( | ||
status="success", | ||
status_code=self.HTTP_STATUS_CREATED, | ||
message="Record saved in DynamoDB", | ||
) | ||
else: | ||
message = response["Error"]["Message"] | ||
self.logger.error(f"Failed saving record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except ClientError as error: | ||
message = f"{error.response['Error']['Message']}. {error.response['Error']['Code']}" | ||
self.logger.error(f"ClientError when saving record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=error.response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except Exception as error: | ||
self.logger.error(f"Exception when saving record: Details: {error}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=self.HTTP_STATUS_INTERNAL_SERVER_ERROR, | ||
message=str(error), | ||
) | ||
|
||
def scan_table(self) -> Dict[str, Any]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont thnk we are going to need this one |
||
"""This function is used to fetch all the records in the table. | ||
If the response from the database is successful, it returns a status of "success". | ||
If not, it returns a status of "error" along with the HTTP status code and details about the error message. | ||
If there is an AWS ClientError, it logs information about the error and also returns a status of "error" along | ||
with the HTTP status code and details about the error message. | ||
|
||
:return: A summary of the put_item action | ||
:rtype: Dict[str, Any] | ||
""" | ||
try: | ||
response = self.table.scan() | ||
if response["ResponseMetadata"]["HTTPStatusCode"] == self.HTTP_STATUS_OK: | ||
self.logger.info("Clients were retrieved from DynamoDB") | ||
return self.build_response_object( | ||
status="success", | ||
status_code=self.HTTP_STATUS_OK, | ||
message=f"{len(response['Items'])} were found in DynamoDB Clients table", | ||
payload=response["Items"], | ||
) | ||
else: | ||
message = response["Error"]["Message"] | ||
self.logger.error(f"Failed saving record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except ClientError as error: | ||
message = f"{error.response['Error']['Message']}. {error.response['Error']['Code']}" | ||
self.logger.error(f"ClientError when saving record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=error.response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except Exception as error: | ||
self.logger.error(f"Exception when saving record: Details: {error}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=self.HTTP_STATUS_INTERNAL_SERVER_ERROR, | ||
message=str(error), | ||
) | ||
|
||
def delete_record(self, key: dict) -> Dict[str, Any]: | ||
""" | ||
This function is used to delete a record from the DynamoDB table. | ||
It takes in a key dictionary as an argument and attempts to delete the item from the database. | ||
If the response from the database is successful, it returns a status of "success". | ||
If there is an AWS ClientError, it logs information about the error and also returns a status of "error" along | ||
with the HTTP status code and details about the error message. | ||
|
||
:param key: The key of the item to be deleted | ||
:type key: dict | ||
:return: A summary of the delete_item action | ||
:rtype: Dict[str, Any] | ||
""" | ||
try: | ||
response = self.table.delete_item(Key=key) | ||
if response["ResponseMetadata"]["HTTPStatusCode"] == self.HTTP_STATUS_OK: | ||
self.logger.info(f"Client with key {key} was deleted from DynamoDB") | ||
return self.build_response_object( | ||
status="success", | ||
status_code=self.HTTP_STATUS_OK, | ||
message="Record deleted from DynamoDB", | ||
) | ||
else: | ||
message = response["Error"]["Message"] | ||
self.logger.error(f"Failed deleting record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except ClientError as error: | ||
message = f"{error.response['Error']['Message']}. {error.response['Error']['Code']}" | ||
self.logger.error(f"ClientError when deleting record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=error.response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except Exception as error: | ||
self.logger.error(f"Exception when deleting record: Details: {error}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=self.HTTP_STATUS_INTERNAL_SERVER_ERROR, | ||
message=str(error), | ||
) | ||
|
||
def update_record(self, item: dict) -> Dict[str, Any]: | ||
""" | ||
This function is used to update a record in the database using put_item. | ||
If the item already exists, it will be updated. | ||
|
||
:param item: Item as dict | ||
:type item: dict | ||
:return: A summary of the put_item action | ||
:rtype: Dict[str, Any] | ||
""" | ||
try: | ||
db_item = json.loads(json.dumps(item), parse_float=Decimal) | ||
response = self.table.put_item(Item=db_item) | ||
if response["ResponseMetadata"]["HTTPStatusCode"] == self.HTTP_STATUS_OK: | ||
self.logger.info("Client was updated in DynamoDB") | ||
return self.build_response_object( | ||
status="success", | ||
status_code=self.HTTP_STATUS_OK, | ||
message="Record updated in DynamoDB", | ||
) | ||
else: | ||
message = response["Error"]["Message"] | ||
self.logger.error(f"Failed updating record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except ClientError as error: | ||
message = f"{error.response['Error']['Message']}. {error.response['Error']['Code']}" | ||
self.logger.error(f"ClientError when updating record: Details: {message}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=error.response["ResponseMetadata"]["HTTPStatusCode"], | ||
message=message, | ||
) | ||
except Exception as error: | ||
self.logger.error(f"Exception when updating record: Details: {error}") | ||
return self.build_response_object( | ||
status="error", | ||
status_code=self.HTTP_STATUS_INTERNAL_SERVER_ERROR, | ||
message=str(error), | ||
) | ||
|
||
def build_response_object( | ||
self, | ||
status: str, | ||
status_code: int, | ||
message: str, | ||
payload: Dict[str, Any] = None, | ||
) -> Dict[str, Any]: | ||
""" | ||
This method maps an status code a message into the response dictionary | ||
|
||
:param status: Success or Error | ||
:type status: str | ||
:param status_code: Http Status Code | ||
:type status_code: int | ||
:param message: A string that contains the message to be returned | ||
:type error_message: str | ||
:param payload: Object with data from DynamoDb | ||
:type error_message: dict | ||
:return: a dictionary with the message | ||
:rtype: Dict[str, Any] | ||
""" | ||
|
||
return { | ||
"status": status, | ||
"status_code": status_code, | ||
"message": message, | ||
"payload": payload, | ||
} |
54 changes: 54 additions & 0 deletions
54
src/clients/client_modules/data_access/geolocation_handler.py
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,54 @@ | ||
# Python's libraries | ||
from typing import Dict | ||
|
||
# Own's modules | ||
from client_modules.utils.aws import AWSClientManager | ||
import settings | ||
|
||
# Third-party libraries | ||
from aws_lambda_powertools import Logger | ||
|
||
|
||
class Geolocation: | ||
|
||
def get_lat_and_long_from_street_address( | ||
self, str_address: str | ||
) -> Dict[str, float]: | ||
"""This function will check AWS Locatio Service to match an address with a geolocation coordinates | ||
|
||
|
||
:param str_address: String representation of the address received by client inside the inputs payload | ||
:type str_address: str | ||
""" | ||
logger = Logger() | ||
if settings.environment != "local": | ||
aws_resources = AWSClientManager() | ||
location = None | ||
if isinstance(str_address, str): | ||
try: | ||
aws_repsonse = aws_resources.location.search_place_index_for_text( | ||
IndexName="HiBerrySearchLocationIndex", Text=str_address | ||
) | ||
if aws_repsonse["ResponseMetadata"]["HTTPStatusCode"] == 200: | ||
logger.info(f"Location: {str_address} found") | ||
lat = aws_repsonse["Results"][0]["Place"]["Geometry"]["Point"][ | ||
1 | ||
] | ||
long = aws_repsonse["Results"][0]["Place"]["Geometry"]["Point"][ | ||
0 | ||
] | ||
location = {"latitude": lat, "longitude": long} | ||
else: | ||
logger.warning("AWS Response was not successfull") | ||
|
||
except Exception as e: | ||
logger.warning( | ||
f"Something failed while fetching data from AWS. Details {e}" | ||
) | ||
else: | ||
logger.warning("Input provided was not a string") | ||
|
||
return location | ||
|
||
else: | ||
return {"latitude": 20.721722843875, "longitude": -103.370054309085} |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is partition key maped from phone to Id?