Skip to content
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

Refactor users service from go to python #596

Open
wants to merge 101 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
67633d5
initial python users
Adibuer-lab May 24, 2024
8c4025c
initial python users
Adibuer-lab May 24, 2024
d385501
dockerise
Adibuer-lab May 28, 2024
d8e042c
dockerise
Adibuer-lab May 28, 2024
5b0a7b1
restructure users-py
Adibuer-lab May 29, 2024
edb9813
test functionality:claim pending
Adibuer-lab May 29, 2024
0ecf15b
test functionality
Adibuer-lab May 29, 2024
8ba9fc7
start editing cfn stack
Adibuer-lab May 30, 2024
9694c3f
start editing cfn stack
Adibuer-lab May 30, 2024
56eeadc
cfn-stack
Adibuer-lab May 30, 2024
7dfe946
rm init_done
Adibuer-lab May 30, 2024
b4794ad
rm init_done
Adibuer-lab May 30, 2024
5597464
add users-service url to deployment-support
Adibuer-lab May 31, 2024
b0f6f8d
switch to python users
Adibuer-lab May 31, 2024
dc52f84
switch to python users buildspec
Adibuer-lab May 31, 2024
c3c76ac
make cloudfront policy names unique across stacks
Adibuer-lab May 31, 2024
67bbf5a
change folder structure
Adibuer-lab May 31, 2024
d9dddac
change folder structure
Adibuer-lab May 31, 2024
8734359
change folder structure
Adibuer-lab May 31, 2024
446c1a7
fix cloudfront names
Adibuer-lab May 31, 2024
f18d1cb
fix identity index name
Adibuer-lab May 31, 2024
40fe809
fix cloudfront names
Adibuer-lab May 31, 2024
887301d
fix table perms
Adibuer-lab May 31, 2024
b8cae11
set empty user
Adibuer-lab Jun 1, 2024
348b76e
set empty user
Adibuer-lab Jun 1, 2024
c2319fd
debug statement
Adibuer-lab Jun 1, 2024
fe877c1
debug statement
Adibuer-lab Jun 1, 2024
fa01276
set default user age to 0
Adibuer-lab Jun 5, 2024
3f5e703
set default persona to ''
Adibuer-lab Jun 5, 2024
90ac2f6
set string defaults to ''
Adibuer-lab Jun 5, 2024
a105d4f
debug statement for update_user_with_data
Adibuer-lab Jun 5, 2024
cbec108
set selectable defaults to false and phone number defaults to ''
Adibuer-lab Jun 5, 2024
97cae3f
parse string timestamps
Adibuer-lab Jun 5, 2024
2e4b3e1
set default persona to ''
Adibuer-lab Jun 5, 2024
198db80
set default strings to '' and not null
Adibuer-lab Jun 5, 2024
f089792
set default strings to '' and not null
Adibuer-lab Jun 5, 2024
34943df
try map for claimed users see if faster
Adibuer-lab Jun 5, 2024
ae8212b
ruff fixes
Adibuer-lab Jun 5, 2024
eb59295
Merge branch 'aws-samples:master' into refactor-users-service-from-go…
Adibuer-lab Jun 7, 2024
5162d49
change to using flask, x-ray, remove init
Adibuer-lab Jun 19, 2024
5124170
change to using flask, x-ray, remove init
Adibuer-lab Jun 19, 2024
bbbae8a
Merge branch 'refactor-users-service-from-go-to-python' of github.com…
Adibuer-lab Jun 19, 2024
a4443e3
change to using flask, x-ray, remove init
Adibuer-lab Jun 19, 2024
885c88c
use app logger
Adibuer-lab Jun 19, 2024
ffe14e8
remove redundant stopiteration exception
Adibuer-lab Jun 19, 2024
9383170
next(User.identity_id_index.query(identity_id), User())
Adibuer-lab Jun 19, 2024
0bb6459
change create and update to upsert
Adibuer-lab Jun 19, 2024
4069b10
change user persona file path to follow standard
Adibuer-lab Jun 19, 2024
515d7b4
fix copy-paste error in user load data lambda
Adibuer-lab Jun 19, 2024
829f3e7
add license comment
Adibuer-lab Jun 19, 2024
c48a7d2
configure x-ray to users service
Adibuer-lab Jun 19, 2024
20ecd37
retry CustomLoadDataUsers
Adibuer-lab Jun 24, 2024
0f1b731
use claimed-index and not local dict
Adibuer-lab Jun 24, 2024
fa1c70b
add health route
Adibuer-lab Jun 24, 2024
736b200
test users init
Adibuer-lab Jun 24, 2024
a67b878
add claimed_user attribute
Adibuer-lab Jun 24, 2024
c2e6c22
increase timeouts
Adibuer-lab Jun 24, 2024
c03fdd5
add retries to product load as well
Adibuer-lab Jun 24, 2024
6e8fbc4
fix datetime values
Adibuer-lab Jun 24, 2024
ab04432
get claimed index gsi working
Adibuer-lab Jun 26, 2024
56d6b64
fix date time handling
Adibuer-lab Jun 26, 2024
cad670e
use upsert for claim user
Adibuer-lab Jun 26, 2024
98da900
debug statement for date
Adibuer-lab Jun 26, 2024
1c704cd
remove duplicate date code
Adibuer-lab Jun 26, 2024
1b8f187
debug statements
Adibuer-lab Jun 26, 2024
c256258
debug statements
Adibuer-lab Jun 26, 2024
27f3ccb
handle none
Adibuer-lab Jun 26, 2024
0685608
parse_Date
Adibuer-lab Jun 26, 2024
a278459
test old prepro
Adibuer-lab Jun 26, 2024
294e46a
check new parse_date
Adibuer-lab Jun 26, 2024
2fbd28d
move date parse to repository
Adibuer-lab Jun 26, 2024
7271243
handle query strings
Adibuer-lab Jun 26, 2024
8cef4a5
use base boto api's
Adibuer-lab Jun 27, 2024
f01326e
use client create table
Adibuer-lab Jun 27, 2024
9362ab0
kl
Adibuer-lab Jun 27, 2024
0092bbd
hj
Adibuer-lab Jun 27, 2024
7e192e7
add new indices for efficient querying
Adibuer-lab Jun 27, 2024
c3a3c13
claim_user type error
Adibuer-lab Jun 27, 2024
6f0ff0e
change index name to persona-index
Adibuer-lab Jun 27, 2024
78c85c9
add new indices to templates
Adibuer-lab Jun 27, 2024
2b8d0ea
change primary_persona index
Adibuer-lab Jun 27, 2024
e92ca86
fix claimed user
Adibuer-lab Jun 27, 2024
a3f1a1a
cleanup
Adibuer-lab Jun 27, 2024
36dd141
use batch write for loading, parallelise later
Adibuer-lab Jun 27, 2024
f4993ed
return empty User for gets
Adibuer-lab Jun 27, 2024
e222c51
remove none if in secondary index gets
Adibuer-lab Jun 27, 2024
7aa9962
return empty user
Adibuer-lab Jun 27, 2024
0584692
make user class flexible
Adibuer-lab Jun 27, 2024
8fe043e
defaults for class
Adibuer-lab Jun 27, 2024
bb3f681
create user function
Adibuer-lab Jun 27, 2024
3fce468
change empty strings
Adibuer-lab Jun 27, 2024
590ca8d
debug statements
Adibuer-lab Jun 27, 2024
492329c
validate posts
Adibuer-lab Jun 27, 2024
231d158
remove empty strings
Adibuer-lab Jun 27, 2024
96d641f
handle personas
Adibuer-lab Jun 27, 2024
1e864a7
for updateUserAttributes except gen
Adibuer-lab Jun 27, 2024
20e44d7
gender must not be null
Adibuer-lab Jun 27, 2024
b0e455f
add random filter to get_random to speed up
Adibuer-lab Jun 27, 2024
896b9f7
reset random retry
Adibuer-lab Jun 27, 2024
3a2d8ff
make random retry count local to function
Adibuer-lab Jun 27, 2024
59599f8
remove useless else
Adibuer-lab Jun 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions aws/cloudformation-templates/base/_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ Outputs:
ProductsTable:
Description: DynamoDB Table for Products
Value: !GetAtt Tables.Outputs.ProductsTable

UsersTable:
Description: DynamoDB Table for Users
Value: !GetAtt Tables.Outputs.UsersTable

CategoriesTable:
Description: DynamoDB Table for Categories
Expand Down
15 changes: 12 additions & 3 deletions aws/cloudformation-templates/base/cloudfront.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ Resources:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub 'retaildemostore-${AWS::Region}'
Name: !Sub
- 'retaildemostore-${Region}-${SafeStackId}'
- Region: !Ref 'AWS::Region'
SafeStackId: !Select [4, !Split ['-', !Ref 'AWS::StackId']]
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
Expand All @@ -101,7 +104,10 @@ Resources:
DefaultTTL: 86400
MaxTTL: 31536000
MinTTL: 86400
Name: !Sub 'retaildemostore-${AWS::Region}'
Name: !Sub
- 'retaildemostore-${Region}-${SafeStackId}'
- Region: !Ref 'AWS::Region'
SafeStackId: !Select [4, !Split ['-', !Ref 'AWS::StackId']]
ParametersInCacheKeyAndForwardedToOrigin:
CookiesConfig:
CookieBehavior: none
Expand All @@ -119,7 +125,10 @@ Resources:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
Name: !Sub 'retaildemostore-${AWS::Region}'
Name: !Sub
- 'retaildemostore-${Region}-${SafeStackId}'
- Region: !Ref 'AWS::Region'
SafeStackId: !Select [4, !Split ['-', !Ref 'AWS::StackId']]
QueryStringsConfig:
QueryStringBehavior: all

Expand Down
59 changes: 59 additions & 0 deletions aws/cloudformation-templates/base/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,61 @@ Description: >
This template deploys the Retail Demo Store DynamoDB Tables.

Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: "id"
AttributeType: "S"
- AttributeName: "username"
AttributeType: "S"
- AttributeName: "identity_id"
AttributeType: "S"
- AttributeName: "claimed_user"
AttributeType: "N"
- AttributeName: "age_range"
AttributeType: "S"
- AttributeName: "primary_persona"
AttributeType: "S"
KeySchema:
- AttributeName: "id"
KeyType: "HASH"
BillingMode: "PAY_PER_REQUEST"
GlobalSecondaryIndexes:
- IndexName: username-index
KeySchema:
- AttributeName: "username"
KeyType: "HASH"
Projection:
ProjectionType: ALL
- IndexName: identity_id-index
KeySchema:
- AttributeName: "identity_id"
KeyType: "HASH"
Projection:
ProjectionType: ALL
- IndexName: claimed-index
KeySchema:
- AttributeName: "claimed_user"
KeyType: "HASH"
Projection:
ProjectionType: ALL
- IndexName: age_range-index
KeySchema:
- AttributeName: "age_range"
KeyType: "HASH"
- AttributeName: "claimed_user"
KeyType: "RANGE"
Projection:
ProjectionType: ALL
- IndexName: primary_persona-index
KeySchema:
- AttributeName: "primary_persona"
KeyType: "HASH"
- AttributeName: "claimed_user"
KeyType: "RANGE"
Projection:
ProjectionType: ALL

ProductsTable:
Type: AWS::DynamoDB::Table
Expand Down Expand Up @@ -147,6 +202,10 @@ Outputs:
Description: DynamoDB Table for Products
Value: !Ref ProductsTable

UsersTable:
Description: DynamoDB Table for Users
Value: !Ref UsersTable

CategoriesTable:
Description: DynamoDB Table for Categories
Value: !Ref CategoriesTable
Expand Down
117 changes: 108 additions & 9 deletions aws/cloudformation-templates/deployment-support.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ Parameters:
ProductsServiceExternalUrl:
Type: String

UsersServiceExternalUrl:
Type: String
Default: none

DeployPersonalizedOffersCampaign:
Type: String
Default: No
Expand Down Expand Up @@ -185,32 +189,127 @@ Resources:
import logging
import cfnresponse
import os
import time


logger = logging.getLogger()
logger.setLevel(logging.INFO)
def is_service_ready(url):
try:
request = Request(f"{url}/", method='GET')
with urlopen(request) as response:
return response.getcode() == 200
except URLError:
return False

def handler(event, context):
response_data = {}
response_status = cfnresponse.SUCCESS

if event['RequestType'] in ['Create', 'Update']:
url = os.environ['ProductsServiceUrl']
request = Request(f"{url}/init", method='POST')
max_retries = 10
retry_delay = 30 # seconds

for attempt in range(max_retries):
if is_service_ready(url):
try:
request = Request(f"{url}/init", method='POST')
with urlopen(request) as response:
logger.info(f"Product Service init method success: {response.read()}")
break # Success, exit the retry loop
except URLError as e:
logger.warning(f"Attempt {attempt + 1}: Error calling product service init: {e.code} : {e.reason}")
else:
logger.warning(f"Attempt {attempt + 1}: Product service not ready yet")

if attempt < max_retries - 1: # Don't sleep on the last attempt
time.sleep(retry_delay)
else:
logger.error(f"Max retries reached. Product service init failed.")
response_status = cfnresponse.FAILED
response_data['Message'] = "Resource creation/update failed: Product service not ready or init failed"

cfnresponse.send(event, context, response_status, response_data)

UsersLoadDataLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: 'Retail Demo Store deployment utility function that calls the users service init endpoint to load users into DynamoDB tables'
Handler: index.handler
Role: !GetAtt LoadDataLambdaRole.Arn
Runtime: python3.12
MemorySize: 128
Timeout: 600
VpcConfig:
SecurityGroupIds:
- !Ref LambdaVpcSecurityGroup
SubnetIds:
- !Ref Subnet1
- !Ref Subnet2
Environment:
Variables:
UsersServiceUrl: !Ref UsersServiceExternalUrl
Code:
ZipFile: |
from urllib.request import Request, urlopen
from urllib.error import URLError
import logging
import cfnresponse
import os
import time

logger = logging.getLogger()
logger.setLevel(logging.INFO)
def is_service_ready(url):
try:
with urlopen(request) as response:
logger.info(f"Product Service init method success: {response.read()}")
except URLError as e:
logger.error(f"Error calling product service init: {e.code} : {e.reason}")
response_status = cfnresponse.FAILED
response_data['Message'] = f"Resource {event['RequestType']} failed: {e}"

request = Request(f"{url}/health", method='GET')
with urlopen(request) as response:
return response.getcode() == 200
except URLError:
return False


def handler(event, context):
response_data = {}
response_status = cfnresponse.SUCCESS

if event['RequestType'] in ['Create', 'Update']:
url = os.environ['UsersServiceUrl']
max_retries = 10
retry_delay = 30 # seconds

for attempt in range(max_retries):
if is_service_ready(url):
try:
request = Request(f"{url}/users/init", method='POST')
with urlopen(request) as response:
logger.info(f"User Service init method success: {response.read()}")
break # Success, exit the retry loop
except URLError as e:
logger.warning(f"Attempt {attempt + 1}: Error calling user service init: {e.code} : {e.reason}")
else:
logger.warning(f"Attempt {attempt + 1}: User service not ready yet")

if attempt < max_retries - 1: # Don't sleep on the last attempt
time.sleep(retry_delay)
else:
logger.error(f"Max retries reached. User service init failed.")
response_status = cfnresponse.FAILED
response_data['Message'] = "Resource creation/update failed: User service not ready or init failed"

cfnresponse.send(event, context, response_status, response_data)

CustomLoadDataProducts:
Type: Custom::CustomLoadData
Properties:
ServiceToken: !GetAtt LoadDataLambdaFunction.Arn

CustomLoadDataUsers:
Type: Custom::CustomLoadData
Properties:
ServiceToken: !GetAtt UsersLoadDataLambdaFunction.Arn

####################### Pre-Create Personalize Resources #######################

PersonalizePreCreateLambdaFunction:
Expand Down
5 changes: 5 additions & 0 deletions aws/cloudformation-templates/services/_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Parameters:
Type: String
Default: none

UsersTable:
Type: String
Default: none

CategoriesTable:
Type: String
Default: none
Expand Down Expand Up @@ -207,6 +211,7 @@ Resources:
ParameterPersonalizeEventTrackerId: !Ref ParameterPersonalizeEventTrackerId
ParameterAmplitudeApiKey: !Ref ParameterAmplitudeApiKey
ParameterOptimizelySdkKey: !Ref ParameterOptimizelySdkKey
UsersTable: !Ref UsersTable
PinpointAppId: !Ref PinpointAppId
CleanupBucketLambdaArn: !Ref CleanupBucketLambdaArn
DeleteRepositoryLambdaArn: !GetAtt DeleteRepositoryLambdaFunction.Arn
Expand Down
5 changes: 5 additions & 0 deletions aws/cloudformation-templates/services/service/_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ Parameters:
Type: String
Default: none

UsersTable:
Type: String
Default: none

CategoriesTable:
Type: String
Default: none
Expand Down Expand Up @@ -287,6 +291,7 @@ Resources:
ParameterIVSVideoChannelMap: !Ref ParameterIVSVideoChannelMap
UseDefaultIVSStreams: !Ref UseDefaultIVSStreams
ProductsTable: !Ref ProductsTable
UsersTable: !Ref UsersTable
CategoriesTable: !Ref CategoriesTable
CartsTable: !Ref CartsTable
OrdersTable: !Ref OrdersTable
Expand Down
8 changes: 8 additions & 0 deletions aws/cloudformation-templates/services/service/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ Parameters:
Type: String
Default: none

UsersTable:
Type: String
Default: none

CategoriesTable:
Type: String
Default: none
Expand Down Expand Up @@ -177,8 +181,10 @@ Resources:
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${CategoriesTable}'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${CartsTable}'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${OrdersTable}'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${UsersTable}'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ExperimentStrategyTable}'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProductsTable}/index/*'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${UsersTable}/index/*'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${PersonalisedProductsTable}/index/*'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${CategoriesTable}/index/*'
- !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${CartsTable}/index/*'
Expand Down Expand Up @@ -393,6 +399,8 @@ Resources:
Value: '443'
- Name: DDB_TABLE_PRODUCTS
Value: !Ref ProductsTable
- Name: DDB_TABLE_USERS
Value: !Ref UsersTable
- Name: DDB_TABLE_PERSONALISED_PRODUCTS
Value: !Ref PersonalisedProductsTable
- Name: DDB_TABLE_CATEGORIES
Expand Down
3 changes: 3 additions & 0 deletions aws/cloudformation-templates/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ Resources:
EnvOpenSearchDomainEndpoint: !GetAtt Base.Outputs.OpenSearchDomainEndpoint
PinpointAppId: !GetAtt Base.Outputs.PinpointAppId
ProductsTable: !GetAtt Base.Outputs.ProductsTable
UsersTable: !GetAtt Base.Outputs.UsersTable
CategoriesTable: !GetAtt Base.Outputs.CategoriesTable
CartsTable: !GetAtt Base.Outputs.CartsTable
OrdersTable: !GetAtt Base.Outputs.OrdersTable
Expand Down Expand Up @@ -905,6 +906,7 @@ Resources:
PinpointEmailFromName: !Ref PinpointEmailFromName
UseDefaultIVSStreams: !Ref UseDefaultIVSStreams
ProductsServiceExternalUrl: !GetAtt Services.Outputs.ProductsServiceUrl
UsersServiceExternalUrl: !GetAtt Services.Outputs.UsersServiceUrl
DeployPersonalizedOffersCampaign: !Ref DeployPersonalizedOffersCampaign
PersonalizeRoleArn: !GetAtt Base.Outputs.PersonalizeRoleArn
StackBucketName: !GetAtt Base.Outputs.StackBucketName
Expand Down Expand Up @@ -1116,6 +1118,7 @@ Outputs:

# Users service
AWS_REGION=${AWS::Region}
DDB_TABLE_USERS=${Base.Outputs.UsersTable}

# Locations service
RESOURCE_BUCKET=${ResourceBucket}
Expand Down
4 changes: 2 additions & 2 deletions generators/generate_interactions_personalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
DISCOUNT_PROBABILITY_WITH_PREFERENCE = 0.5

IN_PRODUCTS_FILENAME = "src/products/data/products.yaml"
IN_USERS_FILENAMES = ["src/users/src/users-service/data/users.json.gz",
"src/users/src/users-service/data/cstore_users.json.gz"]
IN_USERS_FILENAMES = ["src/users/data/users.json.gz",
"src/users/data/cstore_users.json.gz"]

PROGRESS_MONITOR_SECONDS_UPDATE = 30

Expand Down
2 changes: 1 addition & 1 deletion generators/generate_interactions_personalize_offers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
RANDOM_SEED = 1

IN_PRODUCTS_FILENAME = "src/products/data/products.yaml"
IN_USERS_FILENAME = "src/users/src/users-service/data/users.json.gz"
IN_USERS_FILENAME = "src/users/data/users.json.gz"
IN_OFFERS_FILENAME = "src/offers/src/offers-service/data/offers.json"

# Where to put the generated data so that it is picked up by stage.sh
Expand Down
5 changes: 5 additions & 0 deletions src/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ DDB_TABLE_CATEGORIES=categories
DDB_TABLE_PERSONALISED_PRODUCTS=personalisedproducts
DDB_TABLE_CARTS=carts
DDB_TABLE_ORDERS=orders

# Users service variables:
# DynamoDB table names (if connecting to DDB in your AWS account, change accordingly)
DDB_TABLE_USERS=users

# Root URL to use when building fully qualified URLs to product detail view
WEB_ROOT_URL=http://localhost:8080
# Image root URL to use when building fully qualified URLs to product images
Expand Down
Loading
Loading