boto3-assist 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/PKG-INFO +1 -1
  2. boto3_assist-0.1.3/aws_regions_with_status.csv +31 -0
  3. boto3_assist-0.1.3/aws_regions_with_status.json +122 -0
  4. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/devops/readme.md +1 -3
  5. boto3_assist-0.1.1/examples/dynamodb/models/order_item.py → boto3_assist-0.1.3/examples/dynamodb/models/order_item_model.py +6 -5
  6. boto3_assist-0.1.3/examples/dynamodb/models/order_model.py +101 -0
  7. boto3_assist-0.1.1/examples/dynamodb/models/order_model.py → boto3_assist-0.1.3/examples/dynamodb/models/product_model.py +23 -20
  8. boto3_assist-0.1.3/examples/dynamodb/order_example/main.py +168 -0
  9. boto3_assist-0.1.3/examples/dynamodb/order_example/products.json +352 -0
  10. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/order_item_service.py +1 -1
  11. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/order_service.py +36 -12
  12. boto3_assist-0.1.3/examples/ec2/regions_report.py +80 -0
  13. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/pyproject.toml +1 -1
  14. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/requirements-dev.txt +1 -0
  15. boto3_assist-0.1.3/src/boto3_assist/connection_tracker.py +47 -0
  16. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_index.py +18 -6
  17. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_key.py +3 -2
  18. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_model_base.py +8 -0
  19. boto3_assist-0.1.3/src/boto3_assist/ec2/ec2_connection.py +103 -0
  20. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/environment_services/environment_variables.py +16 -0
  21. boto3_assist-0.1.3/src/boto3_assist/version.py +1 -0
  22. boto3_assist-0.1.3/tests/examples_test/__init__.py +0 -0
  23. boto3_assist-0.1.1/examples/dynamodb/order_example/main.py +0 -93
  24. boto3_assist-0.1.1/src/boto3_assist/version.py +0 -1
  25. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.env.development +0 -0
  26. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.env.docker +0 -0
  27. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.env.docker.001 +0 -0
  28. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.env.docker.nosql.workbench +0 -0
  29. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.gitignore +0 -0
  30. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.vscode/launch.json +0 -0
  31. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.vscode/settings.json +0 -0
  32. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/.vscode/tasks.json +0 -0
  33. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/LICENSE-EXPLAINED.txt +0 -0
  34. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/LICENSE.txt +0 -0
  35. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/README.md +0 -0
  36. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/devops/build.py +0 -0
  37. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/__init__.py +0 -0
  38. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/models/user_model.py +0 -0
  39. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/models/user_post_model.py +0 -0
  40. /boto3_assist-0.1.1/src/boto3_assist/__init__.py → /boto3_assist-0.1.3/examples/dynamodb/services/product_service.py +0 -0
  41. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/table_service.py +0 -0
  42. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/user_post_service.py +0 -0
  43. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/user_service.py +0 -0
  44. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/user_service_client_example.py +0 -0
  45. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/services/user_service_resource_example.py +0 -0
  46. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/examples/dynamodb/user_post_example/main.py +0 -0
  47. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/module-headers.txt +0 -0
  48. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/mypy.ini +0 -0
  49. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/requirements.txt +0 -0
  50. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/run-checks.sh +0 -0
  51. {boto3_assist-0.1.1/src/boto3_assist/environment_services → boto3_assist-0.1.3/src/boto3_assist}/__init__.py +0 -0
  52. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/boto3session.py +0 -0
  53. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
  54. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
  55. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_connection_tracker.py +0 -0
  56. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
  57. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
  58. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
  59. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
  60. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
  61. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/readme.md +0 -0
  62. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
  63. {boto3_assist-0.1.1/tests → boto3_assist-0.1.3/src/boto3_assist/environment_services}/__init__.py +0 -0
  64. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/environment_services/environment_loader.py +0 -0
  65. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/utilities/datetime_utility.py +0 -0
  66. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/utilities/logging_utility.py +0 -0
  67. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/utilities/serialization_utility.py +0 -0
  68. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/src/boto3_assist/utilities/string_utility.py +0 -0
  69. {boto3_assist-0.1.1/tests/dynamodb → boto3_assist-0.1.3/tests}/__init__.py +0 -0
  70. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/__top/__init__.py +0 -0
  71. {boto3_assist-0.1.1/tests/examples_test → boto3_assist-0.1.3/tests/dynamodb}/__init__.py +0 -0
  72. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/dynamodb/dynamodb_model_base_test.py +0 -0
  73. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
  74. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/examples_test/user_service_test.py +0 -0
  75. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/utilities/__init__.py +0 -0
  76. {boto3_assist-0.1.1 → boto3_assist-0.1.3}/tests/utilities/serialization_utility_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: boto3_assist
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Additional boto3 wrappers to make your life a little easier
5
5
  Author-email: Eric Wilson <boto3-assist@geekcafe.com>
6
6
  License-File: LICENSE-EXPLAINED.txt
@@ -0,0 +1,31 @@
1
+ RegionName,OptInStatus
2
+ af-south-1,not-opted-in
3
+ ap-east-1,not-opted-in
4
+ ap-northeast-1,opt-in-not-required
5
+ ap-northeast-2,opt-in-not-required
6
+ ap-northeast-3,opt-in-not-required
7
+ ap-south-1,opt-in-not-required
8
+ ap-south-2,not-opted-in
9
+ ap-southeast-1,opt-in-not-required
10
+ ap-southeast-2,opt-in-not-required
11
+ ap-southeast-3,not-opted-in
12
+ ap-southeast-4,not-opted-in
13
+ ap-southeast-5,not-opted-in
14
+ ca-central-1,opt-in-not-required
15
+ ca-west-1,not-opted-in
16
+ eu-central-1,opt-in-not-required
17
+ eu-central-2,not-opted-in
18
+ eu-north-1,opt-in-not-required
19
+ eu-south-1,not-opted-in
20
+ eu-south-2,not-opted-in
21
+ eu-west-1,opt-in-not-required
22
+ eu-west-2,opt-in-not-required
23
+ eu-west-3,opt-in-not-required
24
+ il-central-1,not-opted-in
25
+ me-central-1,not-opted-in
26
+ me-south-1,not-opted-in
27
+ sa-east-1,opt-in-not-required
28
+ us-east-1,opt-in-not-required
29
+ us-east-2,opt-in-not-required
30
+ us-west-1,opt-in-not-required
31
+ us-west-2,opt-in-not-required
@@ -0,0 +1,122 @@
1
+ [
2
+ {
3
+ "RegionName": "af-south-1",
4
+ "OptInStatus": "not-opted-in"
5
+ },
6
+ {
7
+ "RegionName": "ap-east-1",
8
+ "OptInStatus": "not-opted-in"
9
+ },
10
+ {
11
+ "RegionName": "ap-northeast-1",
12
+ "OptInStatus": "opt-in-not-required"
13
+ },
14
+ {
15
+ "RegionName": "ap-northeast-2",
16
+ "OptInStatus": "opt-in-not-required"
17
+ },
18
+ {
19
+ "RegionName": "ap-northeast-3",
20
+ "OptInStatus": "opt-in-not-required"
21
+ },
22
+ {
23
+ "RegionName": "ap-south-1",
24
+ "OptInStatus": "opt-in-not-required"
25
+ },
26
+ {
27
+ "RegionName": "ap-south-2",
28
+ "OptInStatus": "not-opted-in"
29
+ },
30
+ {
31
+ "RegionName": "ap-southeast-1",
32
+ "OptInStatus": "opt-in-not-required"
33
+ },
34
+ {
35
+ "RegionName": "ap-southeast-2",
36
+ "OptInStatus": "opt-in-not-required"
37
+ },
38
+ {
39
+ "RegionName": "ap-southeast-3",
40
+ "OptInStatus": "not-opted-in"
41
+ },
42
+ {
43
+ "RegionName": "ap-southeast-4",
44
+ "OptInStatus": "not-opted-in"
45
+ },
46
+ {
47
+ "RegionName": "ap-southeast-5",
48
+ "OptInStatus": "not-opted-in"
49
+ },
50
+ {
51
+ "RegionName": "ca-central-1",
52
+ "OptInStatus": "opt-in-not-required"
53
+ },
54
+ {
55
+ "RegionName": "ca-west-1",
56
+ "OptInStatus": "not-opted-in"
57
+ },
58
+ {
59
+ "RegionName": "eu-central-1",
60
+ "OptInStatus": "opt-in-not-required"
61
+ },
62
+ {
63
+ "RegionName": "eu-central-2",
64
+ "OptInStatus": "not-opted-in"
65
+ },
66
+ {
67
+ "RegionName": "eu-north-1",
68
+ "OptInStatus": "opt-in-not-required"
69
+ },
70
+ {
71
+ "RegionName": "eu-south-1",
72
+ "OptInStatus": "not-opted-in"
73
+ },
74
+ {
75
+ "RegionName": "eu-south-2",
76
+ "OptInStatus": "not-opted-in"
77
+ },
78
+ {
79
+ "RegionName": "eu-west-1",
80
+ "OptInStatus": "opt-in-not-required"
81
+ },
82
+ {
83
+ "RegionName": "eu-west-2",
84
+ "OptInStatus": "opt-in-not-required"
85
+ },
86
+ {
87
+ "RegionName": "eu-west-3",
88
+ "OptInStatus": "opt-in-not-required"
89
+ },
90
+ {
91
+ "RegionName": "il-central-1",
92
+ "OptInStatus": "not-opted-in"
93
+ },
94
+ {
95
+ "RegionName": "me-central-1",
96
+ "OptInStatus": "not-opted-in"
97
+ },
98
+ {
99
+ "RegionName": "me-south-1",
100
+ "OptInStatus": "not-opted-in"
101
+ },
102
+ {
103
+ "RegionName": "sa-east-1",
104
+ "OptInStatus": "opt-in-not-required"
105
+ },
106
+ {
107
+ "RegionName": "us-east-1",
108
+ "OptInStatus": "opt-in-not-required"
109
+ },
110
+ {
111
+ "RegionName": "us-east-2",
112
+ "OptInStatus": "opt-in-not-required"
113
+ },
114
+ {
115
+ "RegionName": "us-west-1",
116
+ "OptInStatus": "opt-in-not-required"
117
+ },
118
+ {
119
+ "RegionName": "us-west-2",
120
+ "OptInStatus": "opt-in-not-required"
121
+ }
122
+ ]
@@ -20,6 +20,4 @@ twine upload --repository-url https://test.pypi.org/legacy/ dist/*
20
20
 
21
21
  pip install --index-url https://test.pypi.org/simple/ boto3-assist
22
22
 
23
- ```
24
-
25
- pypi-AgEIcHlwaS5vcmcCJGQ0Zjk3ODRmLTgxZDMtNDViNy1iN2QwLWZkZDQzNTEyY2UwMgACKlszLCI3YzZmZTIzYS04MDVkLTQ0YjYtODlkZS04YTJiODJiNjBmMDQiXQAABiBxfnzdKtx6gff_FbuwKKV8D4b7XkUJJESxSqU1_mkCRA
23
+ ```
@@ -8,6 +8,7 @@ import datetime
8
8
  from typing import Optional
9
9
  from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
10
10
  from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
11
+ from examples.dynamodb.models.product_model import Product
11
12
 
12
13
 
13
14
  class OrderItem(DynamoDBModelBase):
@@ -17,12 +18,12 @@ class OrderItem(DynamoDBModelBase):
17
18
  super().__init__()
18
19
  self.id: Optional[str] = None
19
20
  self.order_id: Optional[str] = None
20
-
21
- self.product_id: Optional[str] = None
22
- self.product_name: Optional[str] = None
21
+ self.product: Optional[Product] = None
22
+ # self.product_id: Optional[str] = None
23
+ # self.product_name: Optional[str] = None
23
24
  self.quantity: Optional[int] = 0
24
- self.price: Optional[float] = 0.0
25
- self.is_taxable: bool = False
25
+ # self.price: Optional[float] = 0.0
26
+ # self.is_taxable: bool = False
26
27
  self.is_discounted: bool = False
27
28
  self.created_utc: Optional[datetime.datetime] = None
28
29
  self.modified_utc: Optional[datetime.datetime] = None
@@ -0,0 +1,101 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import datetime
8
+ from typing import Optional
9
+ from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
10
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
11
+
12
+
13
+ class Order(DynamoDBModelBase):
14
+ """Order Model"""
15
+
16
+ def __init__(
17
+ self,
18
+ id: Optional[str] = None, # pylint: disable=w0622
19
+ ) -> None:
20
+ super().__init__()
21
+ self.id: Optional[str] = id
22
+ self.user_id: Optional[str] = None
23
+ self.created_utc: Optional[datetime.datetime] = None
24
+ self.modified_utc: Optional[datetime.datetime] = None
25
+ self.completed_utc: Optional[datetime.datetime] = None
26
+ self.status: Optional[str] = None
27
+ self.total: float = 0
28
+ self.tax_total: float = 0
29
+ self.__setup_indexes()
30
+
31
+ def get_completed_utc_ts(self) -> float | str:
32
+ """Get a time stamp representation of the completed date"""
33
+ if self.completed_utc is None:
34
+ return ""
35
+ return self.completed_utc.timestamp()
36
+
37
+ def get_completed_utc_yyyymmdd(self) -> str:
38
+ """Get a time stamp representation of the completed date"""
39
+ if self.completed_utc is None:
40
+ return "yyyymmdd"
41
+ value = f"{self.completed_utc.year}{str(self.completed_utc.month).zfill(2)}{str(self.completed_utc.day).zfill(2)}"
42
+ return value
43
+
44
+ def __setup_indexes(self):
45
+ # user id
46
+ primay: DynamoDBIndex = DynamoDBIndex()
47
+ primay.name = "primary"
48
+ primay.partition_key.attribute_name = "pk"
49
+ primay.partition_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
50
+ primay.sort_key.attribute_name = "sk"
51
+ primay.sort_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
52
+ self.indexes.add_primary(primay)
53
+
54
+ # all orders on a given day, sort by created date
55
+ self.indexes.add_secondary(
56
+ DynamoDBIndex(
57
+ index_name="gsi0",
58
+ partition_key=DynamoDBKey(
59
+ attribute_name="gsi0_pk",
60
+ value=lambda: DynamoDBKey.build_key(
61
+ (
62
+ "yyyymmdd",
63
+ f"{self.get_completed_utc_yyyymmdd()}",
64
+ )
65
+ ),
66
+ ),
67
+ sort_key=DynamoDBKey(
68
+ attribute_name="gsi0_sk",
69
+ value=lambda: DynamoDBKey.build_key(
70
+ # when dealing with things like timestamps (or any numberfields)
71
+ # you may want to exclude a prefix key
72
+ # e.g. instead of: ("completed_utc_ts", self.get_completed_utc_ts())
73
+ # do the following by passing an empty string
74
+ ("", self.get_completed_utc_ts())
75
+ ),
76
+ ),
77
+ )
78
+ )
79
+
80
+ # all user orders, sort by completed date
81
+ self.indexes.add_secondary(
82
+ DynamoDBIndex(
83
+ index_name="gsi1",
84
+ partition_key=DynamoDBKey(
85
+ attribute_name="gsi1_pk",
86
+ value=lambda: DynamoDBKey.build_key(
87
+ ("user", self.user_id), ("orders", "")
88
+ ),
89
+ ),
90
+ sort_key=DynamoDBKey(
91
+ attribute_name="gsi1_sk",
92
+ value=lambda: DynamoDBKey.build_key(
93
+ # when dealing with things like timestamps (or any numberfields)
94
+ # you may want to exclude a prefix key
95
+ # e.g. instead of: ("completed_utc_ts", self.get_completed_utc_ts())
96
+ # do the following by passing an empty string
97
+ (None, self.get_completed_utc_ts())
98
+ ),
99
+ ),
100
+ )
101
+ )
@@ -10,29 +10,35 @@ from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
10
10
  from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
11
11
 
12
12
 
13
- class Order(DynamoDBModelBase):
14
- """Order Model"""
15
-
16
- def __init__(self, id: Optional[str] = None) -> None:
13
+ class Product(DynamoDBModelBase):
14
+ def __init__(
15
+ self,
16
+ id: Optional[str] = None, # pylint: disable=w0622
17
+ name: Optional[str] = None,
18
+ price: float = 0.0,
19
+ description: Optional[str] = None,
20
+ sku: Optional[str] = None,
21
+ ):
17
22
  super().__init__()
23
+
18
24
  self.id: Optional[str] = id
19
- self.user_id: Optional[str] = None
20
- self.created_utc: Optional[datetime.datetime] = None
21
- self.modified_utc: Optional[datetime.datetime] = None
22
- self.completed_utc: Optional[datetime.datetime] = None
23
- self.status: Optional[str] = None
24
- self.total: float = 0
25
- self.tax_total: float = 0
25
+ self.name: Optional[str] = name
26
+ self.price: float = price
27
+ self.description: Optional[str] = description
28
+ self.sku: Optional[str] = sku
29
+
26
30
  self.__setup_indexes()
27
31
 
32
+ def __str__(self):
33
+ return f"{self.name} - ${self.price}"
34
+
28
35
  def __setup_indexes(self):
29
- # user id
30
36
  primay: DynamoDBIndex = DynamoDBIndex()
31
37
  primay.name = "primary"
32
38
  primay.partition_key.attribute_name = "pk"
33
- primay.partition_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
39
+ primay.partition_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
34
40
  primay.sort_key.attribute_name = "sk"
35
- primay.sort_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
41
+ primay.sort_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
36
42
  self.indexes.add_primary(primay)
37
43
 
38
44
  self.indexes.add_secondary(
@@ -40,15 +46,12 @@ class Order(DynamoDBModelBase):
40
46
  index_name="gsi0",
41
47
  partition_key=DynamoDBKey(
42
48
  attribute_name="gsi0_pk",
43
- value=lambda: DynamoDBKey.build_key(
44
- ("orders", ""), ("user", self.user_id)
45
- ),
49
+ # hot key warning
50
+ value=lambda: DynamoDBKey.build_key(("products", "")),
46
51
  ),
47
52
  sort_key=DynamoDBKey(
48
53
  attribute_name="gsi0_sk",
49
- value=lambda: DynamoDBKey.build_key(
50
- ("completed_utc", self.created_utc)
51
- ),
54
+ value=lambda: DynamoDBKey.build_key(("name", self.name)),
52
55
  ),
53
56
  )
54
57
  )
@@ -0,0 +1,168 @@
1
+ """
2
+ DynamoDB Example
3
+ """
4
+
5
+ import json
6
+ import os
7
+ import random
8
+ from pathlib import Path
9
+ from typing import List
10
+ from datetime import datetime, timedelta, UTC
11
+ from boto3_assist.dynamodb.dynamodb import DynamoDB
12
+ from boto3_assist.environment_services.environment_loader import EnvironmentLoader
13
+ from boto3_assist.utilities.serialization_utility import JsonEncoder
14
+ from boto3_assist.utilities.string_utility import StringUtility
15
+ from examples.dynamodb.services.table_service import DynamoDBTableService
16
+ from examples.dynamodb.services.order_service import OrderService, Order
17
+ from examples.dynamodb.services.order_item_service import OrderItemService, OrderItem
18
+ from examples.dynamodb.models.product_model import Product
19
+
20
+
21
+ class DynamoDBExample:
22
+ """An example of using and debuggin DynamoDB"""
23
+
24
+ def __init__(self, table_name: str) -> None:
25
+ self.db: DynamoDB = DynamoDB()
26
+ self.table_service: DynamoDBTableService = DynamoDBTableService(self.db)
27
+
28
+ self.table_name = table_name
29
+
30
+ self.order_service: OrderService = OrderService(self.db, table_name=table_name)
31
+ self.order_item_service: OrderItemService = OrderItemService(
32
+ self.db, table_name=table_name
33
+ )
34
+
35
+ self.__products: List[Product] = []
36
+ self.order_ids: list[str] = []
37
+
38
+ self.__load_products()
39
+
40
+ def __load_products(self):
41
+ """Load products"""
42
+ path = os.path.join(str(Path(__file__).parent.absolute()), "products.json")
43
+ if os.path.exists(path):
44
+ with open(path, "r", encoding="utf-8") as file:
45
+ prodcut_list = json.load(file)
46
+ self.__products = [Product(**product) for product in prodcut_list]
47
+ else:
48
+ raise FileNotFoundError("Failed to find the products file")
49
+
50
+ def run_examples(self):
51
+ """Run a basic examples with some CRUD examples"""
52
+
53
+ # I'm going to use a single table design pattern but you don't have to
54
+ self.table_service.create_a_table(table_name=self.table_name)
55
+ user_1 = StringUtility.generate_uuid()
56
+ five_days_ago = datetime.now(UTC) - timedelta(days=5)
57
+ four_days_ago = datetime.now(UTC) - timedelta(days=4)
58
+ user_2 = StringUtility.generate_uuid()
59
+ user_3 = StringUtility.generate_uuid()
60
+ user_4 = StringUtility.generate_uuid()
61
+ self.__generate_order(user_id=user_1, override_competed_date_utc=five_days_ago)
62
+ self.__generate_order(user_id=user_1, override_competed_date_utc=four_days_ago)
63
+ self.__generate_order(user_id=user_2)
64
+ self.__generate_order(user_id=user_3)
65
+ self.__generate_order(user_id=user_4)
66
+
67
+ self.__list_orders_from_known_id()
68
+
69
+ low = five_days_ago - timedelta(hours=1)
70
+ high = datetime.now(UTC)
71
+ print(f"checking for orders between {low} and {high} for {user_1}. Expecting 2")
72
+ self.__list_orders_for_user(
73
+ user_id=user_1, start_date_range=low, end_date_range=high
74
+ )
75
+
76
+ low = four_days_ago
77
+ high = low + timedelta(days=1)
78
+ print(
79
+ f"checking for orders between {low} and {high} for {user_1}. Expecting One"
80
+ )
81
+ self.__list_orders_for_user(
82
+ user_id=user_1, start_date_range=low, end_date_range=high
83
+ )
84
+
85
+ def __generate_order(
86
+ self,
87
+ user_id: str = None,
88
+ override_competed_date_utc: datetime | None = None,
89
+ ):
90
+ user_id = user_id or StringUtility.generate_uuid()
91
+ completed_date_utc = override_competed_date_utc or datetime.now(UTC)
92
+ order: Order = OrderService.new_order_object(user_id)
93
+ # technically we don't need to save this first
94
+ self.order_service.save(model=order)
95
+ # store the orders for later use
96
+
97
+ self.order_ids.append(order.id)
98
+ random_product_count = random.randint(1, 15)
99
+ for _ in range(random_product_count):
100
+ product: Product = self.__get_random_product()
101
+ order_item: OrderItem = OrderItem()
102
+ order_item.order_id = order.id
103
+ order_item.id = StringUtility.generate_uuid()
104
+ order_item.product = product
105
+ order_item.quantity = 1
106
+
107
+ self.order_item_service.save(model=order_item)
108
+ order.total += order_item.product.price * order_item.quantity
109
+
110
+ order.completed_utc = completed_date_utc
111
+ self.order_service.save(model=order)
112
+
113
+ def __get_random_product(self) -> Product:
114
+ """Return a random product from the list"""
115
+ if not self.__products:
116
+ raise ValueError("No products available")
117
+ return random.choice(self.__products)
118
+
119
+ def __list_orders_from_known_id(self):
120
+ """List the orders"""
121
+ print("######################################################")
122
+ print("Listing orders - looping through Order Ids")
123
+ for order_id in self.order_ids:
124
+ item: dict = self.order_service.get(
125
+ order_id=order_id, include_order_items=True
126
+ )
127
+ print(json.dumps(item, indent=2, cls=JsonEncoder))
128
+
129
+ print("End / Listing orders - looping through Order Ids")
130
+
131
+ def __list_orders_for_user(
132
+ self,
133
+ user_id: str,
134
+ start_date_range: datetime | None = None,
135
+ end_date_range: datetime | None = None,
136
+ ):
137
+ """List the orders for a user"""
138
+ print("######################################################")
139
+ print(f"Listing orders for user {user_id}")
140
+ items: list[dict] = self.order_service.list(
141
+ user_id=user_id, start_range=start_date_range, end_range=end_date_range
142
+ )
143
+ print(f"Found {len(items)} orders for user {user_id}")
144
+ # for item in items:
145
+ # print(json.dumps(item, indent=2, cls=JsonEncoder))
146
+ print("End / Listing orders for user")
147
+
148
+
149
+ def main():
150
+ """Main"""
151
+ # get an environment file name or default to .env.docker
152
+ env_file_name: str = os.getenv("ENVRIONMENT_FILE", ".env.docker")
153
+ path = os.path.join(str(Path(__file__).parents[3].absolute()), env_file_name)
154
+ el: EnvironmentLoader = EnvironmentLoader()
155
+ if not os.path.exists(path=path):
156
+ raise FileNotFoundError("Failed to find the environmetn file")
157
+ loaded: bool = el.load_environment_file(path)
158
+ if not loaded:
159
+ raise RuntimeError("Failed to load my local environment")
160
+
161
+ table_name = "application_table"
162
+ example: DynamoDBExample = DynamoDBExample(table_name=table_name)
163
+ # load a single table design
164
+ example.run_examples()
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()