Skip to content

Commit a9c22c6

Browse files
committed
refactor: migrate from Poetry to UV for dependency management and update configuration files
1 parent 558567f commit a9c22c6

27 files changed

+1298
-5736
lines changed

unicorn_contracts/Makefile

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#### Global Variables
2-
stackName := $(shell yq -ot '.default.global.parameters.stack_name' samconfig.toml)
2+
stackName := $(shell yq -r '.default.global.parameters.stack_name' samconfig.toml)
3+
34

45
#### Test Variables
56
apiUrl = $(call cf_output,$(stackName),ApiUrl)
@@ -10,13 +11,14 @@ ddbPropertyId = $(call get_ddb_key,create_contract_valid_payload_1)
1011
ci: clean build deploy
1112

1213
build:
14+
ruff format
1315
sam validate --lint
1416
cfn-lint template.yaml -a cfn_lint_serverless.rules
15-
poetry export --without-hashes --format=requirements.txt --output=src/requirements.txt
17+
uv export --no-hashes --format=requirements-txt --output-file=src/requirements.txt
1618
sam build -c $(DOCKER_OPTS)
1719

1820
deps:
19-
poetry install
21+
uv sync
2022

2123
deploy: deps build
2224
sam deploy
@@ -25,10 +27,10 @@ deploy: deps build
2527
test: unit-test integration-test
2628

2729
unit-test:
28-
poetry run pytest tests/unit/
30+
uv run pytest tests/unit/
2931

3032
integration-test: deps
31-
poetry run pytest tests/integration/
33+
uv run pytest tests/integration/
3234

3335
curl-test: clean-tests
3436
$(call runif,CREATE CONTRACT)
@@ -68,11 +70,11 @@ clean:
6870
delete:
6971
sam delete --stack-name $(stackName) --no-prompts
7072

71-
# NOTE: [2023-05-09] This is a fix for installing Poetry dependencies in GitHub Actions
73+
# NOTE: [2023-05-09] This is a fix for installing uv dependencies in GitHub Actions
7274
ci_init:
73-
poetry export --without-hashes --format=requirements.txt --output=src/requirements.txt --with dev
74-
poetry run pip install -r src/requirements.txt
75-
poetry install -n
75+
uv export --no-hashes --format=requirements.txt --output-file=src/requirements.txt --extra=dev
76+
uv pip install -r src/requirements.txt
77+
uv sync
7678

7779

7880
#### Helper Functions

unicorn_contracts/poetry.lock

Lines changed: 0 additions & 1838 deletions
This file was deleted.

unicorn_contracts/pyproject.toml

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
1-
[tool.poetry]
1+
[project]
22
name = "contracts_service"
33
version = "0.2.0"
44
description = "Unicorn Properties Contact Service"
5-
authors = ["Amazon Web Services"]
6-
packages = [{ include = "contracts_service", from = "src" }]
5+
authors = [
6+
{name = "Amazon Web Services"}
7+
]
8+
readme = "README.md"
9+
requires-python = ">=3.13"
710

8-
[tool.poetry.dependencies]
9-
python = "^3.11"
10-
boto3 = "^1.35.14"
11-
aws-lambda-powertools = {extras = ["tracer"], version = "^2.43.1"}
12-
aws-xray-sdk = "^2.14.0"
13-
tomli = "^2.0.0"
11+
dependencies = [
12+
"aws-lambda-powertools[tracer]>=3.9.0",
13+
"aws-xray-sdk>=2.14.0",
14+
"boto3>=1.37.23",
15+
]
1416

15-
[tool.poetry.group.dev.dependencies]
16-
pytest = "^8.3.2"
17-
requests = "^2.32.3"
18-
moto = {version = "^5.0.14", extras = ["dynamodb", "events", "sqs"]}
19-
importlib-metadata = "^8.4.0"
20-
pyyaml = "^6.0.2"
21-
arnparse = "^0.0.2"
22-
aws-lambda-powertools = {extras = ["tracer"], version = "^2.43.1"}
23-
poetry-plugin-export = "^1.8.0"
17+
[dependency-groups]
18+
dev = [
19+
"aws-lambda-powertools[all]>=3.9.0",
20+
"requests>=2.32.3",
21+
"moto[dynamodb,events,sqs]>=5.0.14",
22+
"importlib-metadata>=8.4.0",
23+
"pyyaml>=6.0.2",
24+
"arnparse>=0.0.2",
25+
"pytest>=8.3.4",
26+
"ruff>=0.9.7",
27+
]
2428

25-
[build-system]
26-
requires = ["poetry-core>=1.0.0"]
27-
build-backend = "poetry.core.masonry.api"
28-
29-
[tool.pytest.ini_options]
30-
minversion = "8.0"
31-
addopts = "-ra -vv -W ignore::UserWarning"
32-
testpaths = ["tests/unit", "tests/integration"]
33-
34-
[tool.ruff]
35-
line-length = 150
29+
[tool.setuptools]
30+
package-dir = {"contracts_service" = "src"}
31+
packages = ["contracts_service"]

unicorn_contracts/ruff.toml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Exclude files/directories from analysis
2+
exclude = [
3+
".bzr",
4+
".direnv",
5+
".eggs",
6+
".git",
7+
".git-rewrite",
8+
".hg",
9+
".ipynb_checkpoints",
10+
".mypy_cache",
11+
".nox",
12+
".pants.d",
13+
".pyenv",
14+
".pytest_cache",
15+
".pytype",
16+
".ruff_cache",
17+
".svn",
18+
".tox",
19+
".venv",
20+
".vscode",
21+
"__pypackages__",
22+
"_build",
23+
"buck-out",
24+
"build",
25+
"dist",
26+
"node_modules",
27+
"site-packages",
28+
"venv",
29+
]
30+
31+
# Same as Black.
32+
line-length = 120
33+
indent-width = 4
34+
35+
fix = true
36+
37+
[format]
38+
# Like Black, use double quotes for strings.
39+
quote-style = "double"
40+
41+
# Like Black, indent with spaces, rather than tabs.
42+
indent-style = "space"
43+
44+
# Like Black, respect magic trailing commas.
45+
skip-magic-trailing-comma = false
46+
47+
# Like Black, automatically detect the appropriate line ending.
48+
line-ending = "auto"
49+
50+
51+
[lint]
52+
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
53+
select = ["E4", "E7", "E9", "F", "B"]
54+
55+
# 2. Avoid enforcing line-length violations (`E501`)
56+
ignore = ["E501"]
57+
58+
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
59+
unfixable = ["B"]
60+
61+
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories.
62+
[lint.per-file-ignores]
63+
"__init__.py" = ["E402"]
64+
"**/{tests,docs,tools}/*" = ["E402"]

unicorn_contracts/samconfig.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ watch = true
2727
warm_containers = "EAGER"
2828

2929
[default.local_start_lambda.parameters]
30-
warm_containers = "EAGER"
30+
warm_containers = "EAGER"

unicorn_contracts/src/contracts_service/contract_event_handler.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@
3434
table = dynamodb.Table(DYNAMODB_TABLE) # type: ignore
3535

3636

37-
@metrics.log_metrics(capture_cold_start_metric=True) # type: ignore
37+
@metrics.log_metrics(capture_cold_start_metric=True) # type: ignore
3838
@logger.inject_lambda_context
3939
@tracer.capture_method
4040
@event_source(data_class=SQSEvent)
4141
def lambda_handler(event: SQSEvent, context: LambdaContext):
4242
# Multiple records can be delivered in a single event
4343
for record in event.records:
44-
http_method = record.message_attributes.get('HttpMethod', {}).get('stringValue')
44+
http_method = record.message_attributes.get("HttpMethod", {}).get("stringValue")
4545

46-
if http_method == 'POST':
46+
if http_method == "POST":
4747
create_contract(record.json_body)
48-
elif http_method == 'PUT':
48+
elif http_method == "PUT":
4949
update_contract(record.json_body)
5050
else:
51-
raise Exception(f'Unable to handle HttpMethod {http_method}')
51+
raise Exception(f"Unable to handle HttpMethod {http_method}")
5252

5353

5454
@tracer.capture_method
@@ -78,37 +78,39 @@ def create_contract(event: dict) -> None:
7878

7979
current_date = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
8080
contract = {
81-
"property_id": event["property_id"], # PK
82-
"address": event["address"],
83-
"seller_name": event["seller_name"],
84-
"contract_created": current_date,
85-
"contract_last_modified_on": current_date,
86-
"contract_id": str(uuid.uuid4()),
87-
"contract_status": ContractStatus.DRAFT.name,
81+
"property_id": event["property_id"], # PK
82+
"address": event["address"],
83+
"seller_name": event["seller_name"],
84+
"contract_created": current_date,
85+
"contract_last_modified_on": current_date,
86+
"contract_id": str(uuid.uuid4()),
87+
"contract_status": ContractStatus.DRAFT.name,
8888
}
8989

9090
logger.info(msg={"Creating contract": contract, "From event": event})
9191

9292
try:
9393
response = table.put_item(
9494
Item=contract,
95-
ConditionExpression=
96-
Attr('property_id').not_exists()
97-
| Attr('contract_status').is_in([
98-
ContractStatus.CANCELLED.name,
99-
ContractStatus.CLOSED.name,
100-
ContractStatus.EXPIRED.name,
101-
]))
95+
ConditionExpression=Attr("property_id").not_exists()
96+
| Attr("contract_status").is_in(
97+
[
98+
ContractStatus.CANCELLED.name,
99+
ContractStatus.CLOSED.name,
100+
ContractStatus.EXPIRED.name,
101+
]
102+
),
103+
)
102104
logger.info(f'var:response - "{response}"')
103-
105+
104106
# Annotate trace with contract status
105107
tracer.put_annotation(key="ContractStatus", value=contract["contract_status"])
106108

107109
except ClientError as e:
108110
code = e.response["Error"]["Code"]
109-
if code == 'ConditionalCheckFailedException':
111+
if code == "ConditionalCheckFailedException":
110112
logger.info(f"""
111-
Unable to create contract for Property {contract['property_id']}.
113+
Unable to create contract for Property {contract["property_id"]}.
112114
There already is a contract for this property in status {ContractStatus.DRAFT.name} or {ContractStatus.APPROVED.name}
113115
""")
114116
else:
@@ -149,29 +151,27 @@ def update_contract(contract: dict) -> None:
149151

150152
response = table.update_item(
151153
Key={
152-
'property_id': contract['property_id'],
154+
"property_id": contract["property_id"],
153155
},
154156
UpdateExpression="set contract_status=:t, modified_date=:m",
155-
ConditionExpression=
156-
Attr('property_id').exists()
157-
& Attr('contract_status').is_in([
158-
ContractStatus.DRAFT.name
159-
]),
157+
ConditionExpression=Attr("property_id").exists()
158+
& Attr("contract_status").is_in([ContractStatus.DRAFT.name]),
160159
ExpressionAttributeValues={
161-
':t': contract['contract_status'],
162-
':m': current_date,
160+
":t": contract["contract_status"],
161+
":m": current_date,
163162
},
164-
ReturnValues="UPDATED_NEW")
163+
ReturnValues="UPDATED_NEW",
164+
)
165165
logger.info(f'var:response - "{response}"')
166-
166+
167167
# Annotate trace with contract status
168168
tracer.put_annotation(key="ContractStatus", value=contract["contract_status"])
169169

170170
except ClientError as e:
171171
code = e.response["Error"]["Code"]
172-
if code == 'ConditionalCheckFailedException':
172+
if code == "ConditionalCheckFailedException":
173173
logger.exception(f"Unable to update contract Id {contract['property_id']}. Status is not in status DRAFT")
174-
elif code == 'ResourceNotFoundException':
174+
elif code == "ResourceNotFoundException":
175175
logger.exception(f"Unable to update contract Id {contract['property_id']}. Not Found")
176176
else:
177177
raise e

unicorn_contracts/src/contracts_service/enums.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
class ContractStatus(Enum):
88
"""Contract status Enum
9-
9+
1010
APPROVED The contract record is approved.
1111
CANCELLED The contract record is canceled or terminated. You cannot modify a contract record that has this status value.
1212
CLOSED The contract record is closed and all its terms and conditions are met.
@@ -15,12 +15,13 @@ class ContractStatus(Enum):
1515
EXPIRED The contract record is expired. The end date for the contract has passed.
1616
You cannot modify a contract record that has this status value.
1717
You can change the status from expire to pending revision by revising the expired contract.
18-
18+
1919
Parameters
2020
----------
2121
Enum : _type_
2222
_description_
2323
"""
24+
2425
APPROVED = 1
2526
CANCELLED = 2
2627
CLOSED = 3

unicorn_contracts/src/contracts_service/exceptions.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,5 @@ def __init__(self, message=None, status_code=None, details=None):
1515
self.message = message or "No contract found for specified Property ID"
1616
self.status_code = status_code or 400
1717
self.details = details or {}
18-
19-
self.apigw_return = {
20-
"statusCode": self.status_code,
21-
"body": json.dumps({"message": self.message})
22-
}
18+
19+
self.apigw_return = {"statusCode": self.status_code, "body": json.dumps({"message": self.message})}

unicorn_contracts/template.yaml

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: MIT-0
3-
AWSTemplateFormatVersion: "2010-09-09"
3+
AWSTemplateFormatVersion: 2010-09-09
44
Transform:
55
- AWS::Serverless-2016-10-31
66
Description: >
@@ -15,7 +15,8 @@ Metadata:
1515
- WS2001 # Rule disabled because check does not support !ToJsonString transform
1616
- ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure
1717
- W3002 # Rule disabled as nested templates are being packaged
18-
18+
- E0002
19+
1920
Parameters:
2021
Stage:
2122
Type: String
@@ -39,12 +40,13 @@ Mappings:
3940

4041
Conditions:
4142
IsProd: !Equals [!Ref Stage, prod]
43+
IsProd: !Equals [!Ref Stage, prod]
4244

4345
Globals:
4446
Api:
4547
OpenApiVersion: 3.0.1
4648
Function:
47-
Runtime: python3.11
49+
Runtime: python3.13
4850
MemorySize: 128
4951
Timeout: 3
5052
Tracing: Active
@@ -132,16 +134,8 @@ Resources:
132134
ThrottlingRateLimit: 100
133135
AccessLogSetting:
134136
DestinationArn: !GetAtt UnicornContractsApiLogGroup.Arn
135-
Format: !Join
136-
- ""
137-
- - '{"requestId":"$context.requestId",'
138-
- '"integration-error":"$context.integration.error",'
139-
- '"integration-status":"$context.integration.status",'
140-
- '"integration-latency":"$context.integration.latency",'
141-
- '"integration-requestId":"$context.integration.requestId",'
142-
- '"integration-integrationStatus":"$context.integration.integrationStatus",'
143-
- '"response-latency":"$context.responseLatency",'
144-
- '"status":"$context.status"}'
137+
Format: >
138+
{"requestId": $context.requestId, "integration-error": $context.integration.error, "integration-status": $context.integration.status, "integration-latency": $context.integration.latency, "integration-requestId": $context.integration.requestId, "integration-integrationStatus": $context.integration.integrationStatus, "response-latency": $context.responseLatency, "status": $context.status}
145139
DefinitionBody: !Transform
146140
Name: "AWS::Include"
147141
Parameters:

0 commit comments

Comments
 (0)