velocity-python 0.0.78__tar.gz → 0.0.80__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

Files changed (65) hide show
  1. {velocity_python-0.0.78 → velocity_python-0.0.80}/PKG-INFO +2 -1
  2. {velocity_python-0.0.78 → velocity_python-0.0.80}/pyproject.toml +2 -1
  3. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/__init__.py +2 -0
  5. velocity_python-0.0.80/src/velocity/aws/amplify.py +422 -0
  6. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity_python.egg-info/PKG-INFO +2 -1
  7. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  8. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity_python.egg-info/requires.txt +1 -0
  9. {velocity_python-0.0.78 → velocity_python-0.0.80}/LICENSE +0 -0
  10. {velocity_python-0.0.78 → velocity_python-0.0.80}/README.md +0 -0
  11. {velocity_python-0.0.78 → velocity_python-0.0.80}/setup.cfg +0 -0
  12. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/handlers/__init__.py +0 -0
  13. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/handlers/context.py +0 -0
  14. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  15. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/handlers/response.py +0 -0
  16. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  17. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/__init__.py +0 -0
  18. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/__init__.py +0 -0
  19. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/column.py +0 -0
  20. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/database.py +0 -0
  21. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/decorators.py +0 -0
  22. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/engine.py +0 -0
  23. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/exceptions.py +0 -0
  24. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/result.py +0 -0
  25. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/row.py +0 -0
  26. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/sequence.py +0 -0
  27. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/table.py +0 -0
  28. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/core/transaction.py +0 -0
  29. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/__init__.py +0 -0
  30. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/mysql.py +0 -0
  31. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/mysql_reserved.py +0 -0
  32. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/postgres/__init__.py +0 -0
  33. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/postgres/operators.py +0 -0
  34. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/postgres/reserved.py +0 -0
  35. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/postgres/sql.py +0 -0
  36. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/postgres/types.py +0 -0
  37. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/sqlite.py +0 -0
  38. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/sqlite_reserved.py +0 -0
  39. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/sqlserver.py +0 -0
  40. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/sqlserver_reserved.py +0 -0
  41. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/db/servers/tablehelper.py +0 -0
  42. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/__init__.py +0 -0
  43. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/conv/__init__.py +0 -0
  44. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/conv/iconv.py +0 -0
  45. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/conv/oconv.py +0 -0
  46. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/db.py +0 -0
  47. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/export.py +0 -0
  48. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/format.py +0 -0
  49. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/mail.py +0 -0
  50. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/merge.py +0 -0
  51. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/timer.py +0 -0
  52. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity/misc/tools.py +0 -0
  53. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  54. {velocity_python-0.0.78 → velocity_python-0.0.80}/src/velocity_python.egg-info/top_level.txt +0 -0
  55. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_db.py +0 -0
  56. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_email_processing.py +0 -0
  57. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_format.py +0 -0
  58. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_iconv.py +0 -0
  59. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_merge.py +0 -0
  60. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_oconv.py +0 -0
  61. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_postgres.py +0 -0
  62. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_response.py +0 -0
  63. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_spreadsheet_functions.py +0 -0
  64. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_sql_builder.py +0 -0
  65. {velocity_python-0.0.78 → velocity_python-0.0.80}/tests/test_timer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.78
3
+ Version: 0.0.80
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Paul Perez <pperez@codeclubs.org>
6
6
  Project-URL: Homepage, https://codeclubs.org/projects/velocity
@@ -14,6 +14,7 @@ Requires-Dist: requests
14
14
  Requires-Dist: jinja2
15
15
  Requires-Dist: xlrd
16
16
  Requires-Dist: openpyxl
17
+ Requires-Dist: sqlparse
17
18
  Provides-Extra: mysql
18
19
  Requires-Dist: mysql-connector-python; extra == "mysql"
19
20
  Provides-Extra: sqlserver
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "velocity-python"
3
- version = "0.0.78"
3
+ version = "0.0.80"
4
4
  authors = [
5
5
  { name="Paul Perez", email="pperez@codeclubs.org" },
6
6
  ]
@@ -17,6 +17,7 @@ dependencies = [
17
17
  'jinja2',
18
18
  'xlrd',
19
19
  'openpyxl',
20
+ 'sqlparse'
20
21
  ]
21
22
 
22
23
  [project.urls]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.78"
1
+ __version__ = version = "0.0.80"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import requests
3
3
 
4
+ from velocity.aws.amplify import AmplifyProject
5
+
4
6
  DEBUG = (os.environ.get("ENV") != "production") or (os.environ.get("DEBUG") == "Y")
5
7
 
6
8
 
@@ -0,0 +1,422 @@
1
+ import boto3
2
+
3
+
4
+ class AmplifyProject:
5
+ def __init__(self, app_id: str):
6
+ self.app_id = app_id
7
+ self.amplify_client = boto3.client("amplify")
8
+ self.lambda_client = boto3.client("lambda")
9
+ self.sqs_client = boto3.client("sqs")
10
+ self.sqs_resource = boto3.resource("sqs")
11
+
12
+ # Verify app exists
13
+ try:
14
+ response = self.amplify_client.get_app(appId=self.app_id)
15
+ self.app_name = response["app"]["name"]
16
+ except self.amplify_client.exceptions.NotFoundException:
17
+ raise ValueError(f"Amplify app with ID '{self.app_id}' does not exist.")
18
+
19
+ def get_app_name(self):
20
+ return self.app_name
21
+
22
+ def get_region(self):
23
+ return self.amplify_client.meta.region_name
24
+
25
+ def get_account_id(self):
26
+ sts_client = boto3.client("sts")
27
+ return sts_client.get_caller_identity()["Account"]
28
+
29
+ def list_backend_branches(self):
30
+ branches = []
31
+ paginator = self.amplify_client.get_paginator("list_branches")
32
+ for page in paginator.paginate(appId=self.app_id):
33
+ for branch in page.get("branches", []):
34
+ branches.append(branch["branchName"])
35
+ return branches
36
+
37
+ def filtered_env_vars(self, raw_env_vars):
38
+ return {k: v for k, v in raw_env_vars.items() if not k.startswith("_")}
39
+
40
+ def get_merged_env_vars(self, branch):
41
+ all_vars = {}
42
+
43
+ # Global env vars
44
+ app_response = self.amplify_client.get_app(appId=self.app_id)
45
+ global_vars = self.filtered_env_vars(
46
+ app_response["app"].get("environmentVariables", {})
47
+ )
48
+ # print("📦 Global env vars (All branches):")
49
+ # for k, v in global_vars.items():
50
+ # print(f" {k}: {v}")
51
+ all_vars.update(global_vars)
52
+
53
+ # Branch-specific vars
54
+ try:
55
+ branch_response = self.amplify_client.get_branch(
56
+ appId=self.app_id, branchName=branch
57
+ )
58
+ branch_vars = self.filtered_env_vars(
59
+ branch_response["branch"].get("environmentVariables", {})
60
+ )
61
+ # print(f"🌿 Branch-specific env vars for '{branch}':")
62
+ # for k, v in branch_vars.items():
63
+ # print(f" {k}: {v}")
64
+ all_vars.update(branch_vars)
65
+ except self.amplify_client.exceptions.BadRequestException:
66
+ print(f"⚠️ Branch '{branch}' not found. Skipping branch-level overrides.")
67
+
68
+ return all_vars
69
+
70
+ def list_lambda_functions_filtered(self, env_name):
71
+ paginator = self.lambda_client.get_paginator("list_functions")
72
+ for page in paginator.paginate():
73
+ for fn in page["Functions"]:
74
+ name = fn["FunctionName"]
75
+ if self.app_name in name and env_name in name:
76
+ yield fn
77
+
78
+ def update_lambda_function(
79
+ self,
80
+ function_name,
81
+ replace_env_vars=None,
82
+ merge_env_vars=None,
83
+ replace_vpc_config=None,
84
+ merge_vpc_config=None,
85
+ subnet_ids=None,
86
+ security_group_ids=None,
87
+ description=None,
88
+ role=None,
89
+ handler=None,
90
+ runtime=None,
91
+ timeout=None,
92
+ memory_size=None,
93
+ dead_letter_config=None,
94
+ kms_key_arn=None,
95
+ tracing_config=None,
96
+ revision_id=None,
97
+ layers=None,
98
+ file_system_configs=None,
99
+ image_config=None,
100
+ ephemeral_storage=None,
101
+ environment_secrets=None,
102
+ ):
103
+ # Fetch existing configuration
104
+ config = self.lambda_client.get_function_configuration(
105
+ FunctionName=function_name
106
+ )
107
+
108
+ update_params = {"FunctionName": function_name}
109
+
110
+ # Environment variables: replace OR merge
111
+ if replace_env_vars is not None and merge_env_vars is not None:
112
+ raise ValueError(
113
+ "Cannot specify both replace_env_vars and merge_env_vars at the same time."
114
+ )
115
+
116
+ if replace_env_vars is not None:
117
+ update_params["Environment"] = {"Variables": replace_env_vars}
118
+ elif merge_env_vars is not None:
119
+ existing_env = config.get("Environment", {}).get("Variables", {})
120
+ merged_env = {**existing_env, **merge_env_vars}
121
+ update_params["Environment"] = {"Variables": merged_env}
122
+
123
+ # Environment secrets (always merge style)
124
+ if environment_secrets is not None:
125
+ existing_secrets = config.get("Environment", {}).get("Secrets", {})
126
+ merged_secrets = {**existing_secrets, **environment_secrets}
127
+ if "Environment" not in update_params:
128
+ update_params["Environment"] = {}
129
+ update_params["Environment"]["Secrets"] = merged_secrets
130
+
131
+ # VPC config: replace OR merge
132
+ if replace_vpc_config is not None and merge_vpc_config is not None:
133
+ raise ValueError(
134
+ "Cannot specify both replace_vpc_config and merge_vpc_config at the same time."
135
+ )
136
+
137
+ vpc_config_to_apply = None
138
+
139
+ if replace_vpc_config is not None:
140
+ vpc_config_to_apply = replace_vpc_config
141
+ else:
142
+ # Start with existing or empty
143
+ existing_vpc = config.get("VpcConfig", {})
144
+ # print(f"Existing VPC config for {function_name}: {existing_vpc}")
145
+ if "VpcId" in existing_vpc:
146
+ del existing_vpc["VpcId"] # Remove VpcId if present
147
+ vpc_config_to_apply = dict(existing_vpc)
148
+
149
+ if merge_vpc_config is not None:
150
+ vpc_config_to_apply.update(merge_vpc_config)
151
+
152
+ if subnet_ids is not None:
153
+ vpc_config_to_apply["SubnetIds"] = subnet_ids
154
+
155
+ if security_group_ids is not None:
156
+ vpc_config_to_apply["SecurityGroupIds"] = security_group_ids
157
+
158
+ # If vpc_config_to_apply has any keys, add to update_params
159
+ if vpc_config_to_apply and any(vpc_config_to_apply.values()):
160
+ update_params["VpcConfig"] = vpc_config_to_apply
161
+
162
+ # Other parameters
163
+ if description is not None:
164
+ update_params["Description"] = description
165
+
166
+ if role is not None:
167
+ update_params["Role"] = role
168
+
169
+ if handler is not None:
170
+ update_params["Handler"] = handler
171
+
172
+ if runtime is not None:
173
+ update_params["Runtime"] = runtime
174
+
175
+ if timeout is not None:
176
+ update_params["Timeout"] = timeout
177
+
178
+ if memory_size is not None:
179
+ update_params["MemorySize"] = memory_size
180
+
181
+ if dead_letter_config is not None:
182
+ update_params["DeadLetterConfig"] = dead_letter_config
183
+
184
+ if kms_key_arn is not None:
185
+ update_params["KMSKeyArn"] = kms_key_arn
186
+
187
+ if tracing_config is not None:
188
+ update_params["TracingConfig"] = tracing_config
189
+
190
+ if revision_id is not None:
191
+ update_params["RevisionId"] = revision_id
192
+
193
+ if layers is not None:
194
+ update_params["Layers"] = layers
195
+
196
+ if file_system_configs is not None:
197
+ update_params["FileSystemConfigs"] = file_system_configs
198
+
199
+ if image_config is not None:
200
+ update_params["ImageConfig"] = image_config
201
+
202
+ if ephemeral_storage is not None:
203
+ update_params["EphemeralStorage"] = ephemeral_storage
204
+
205
+ # Call update if needed
206
+ if len(update_params) > 1: # FunctionName is always present
207
+ self.lambda_client.update_function_configuration(**update_params)
208
+ else:
209
+ print(f"No updates provided for Lambda function '{function_name}'.")
210
+
211
+ def set_environment_variable(self, key: str, value: str, branch: str = None):
212
+ if branch:
213
+ response = self.amplify_client.get_branch(
214
+ appId=self.app_id, branchName=branch
215
+ )
216
+ env_vars = response["branch"].get("environmentVariables", {})
217
+ env_vars[key] = value
218
+ self.amplify_client.update_branch(
219
+ appId=self.app_id, branchName=branch, environmentVariables=env_vars
220
+ )
221
+ else:
222
+ response = self.amplify_client.get_app(appId=self.app_id)
223
+ env_vars = response["app"].get("environmentVariables", {})
224
+ env_vars[key] = value
225
+ self.amplify_client.update_app(
226
+ appId=self.app_id, environmentVariables=env_vars
227
+ )
228
+
229
+ def update_custom_redirect_rules(self):
230
+ srv_list = [
231
+ "css",
232
+ "gif",
233
+ "ico",
234
+ "jpg",
235
+ "js",
236
+ "json",
237
+ "map",
238
+ "otf",
239
+ "png",
240
+ "svg",
241
+ "ttf",
242
+ "txt",
243
+ "webp",
244
+ "woff",
245
+ "xml",
246
+ "pdf",
247
+ ]
248
+ self.amplify_client.update_app(
249
+ appId=self.app_id,
250
+ customRules=[
251
+ {"source": "/<*>", "target": "/index.html", "status": "404-200"},
252
+ {
253
+ "source": f'</^[^.]+$|\.(?!({"|".join(srv_list)})$)([^.]+$)/>',
254
+ "target": "/",
255
+ "status": "200",
256
+ },
257
+ ],
258
+ )
259
+
260
+ def get_sqs_policy_template(
261
+ self,
262
+ queue_name: str,
263
+ queue_producers: str,
264
+ queue_handler: str,
265
+ ) -> str:
266
+ account_id = self.get_account_id()
267
+ region = self.get_region()
268
+ roles = [qp["Role"] for qp in queue_producers]
269
+ return f"""
270
+ {{
271
+ "Version": "2008-10-17",
272
+ "Id": "__default_policy_ID",
273
+ "Statement": [
274
+ {{
275
+ "Sid": "__owner_statement",
276
+ "Effect": "Allow",
277
+ "Principal": {{
278
+ "AWS": "arn:aws:iam::{account_id}:root"
279
+ }},
280
+ "Action": "SQS:*",
281
+ "Resource": "arn:aws:sqs:{region}:{account_id}:{queue_name}"
282
+ }},
283
+ {{
284
+ "Sid": "__sender_statement",
285
+ "Effect": "Allow",
286
+ "Principal": {{
287
+ "AWS": {repr(roles).replace("'", '"')}
288
+ }},
289
+ "Action": [
290
+ "SQS:GetQueueAttributes",
291
+ "SQS:SendMessage"
292
+ ],
293
+ "Resource": "arn:aws:sqs:{region}:{account_id}:{queue_name}"
294
+ }},
295
+ {{
296
+ "Sid": "__receiver_statement",
297
+ "Effect": "Allow",
298
+ "Principal": {{
299
+ "AWS": "{queue_handler["Role"]}"
300
+ }},
301
+ "Action": [
302
+ "SQS:GetQueueAttributes",
303
+ "SQS:ChangeMessageVisibility",
304
+ "SQS:DeleteMessage",
305
+ "SQS:ReceiveMessage"
306
+ ],
307
+ "Resource": "arn:aws:sqs:{region}:{account_id}:{queue_name}"
308
+ }}
309
+ ]
310
+ }}"""
311
+
312
+ def setup_sqs_queue_and_permissions(
313
+ self,
314
+ branch: str,
315
+ queue_name: str,
316
+ queue_producers: list,
317
+ queue_handler: object,
318
+ use_reserved_concurrency: bool = True,
319
+ ):
320
+ account_id = self.get_account_id()
321
+ region = self.get_region()
322
+
323
+ policy_json = self.get_sqs_policy_template(
324
+ queue_name,
325
+ queue_producers,
326
+ queue_handler,
327
+ )
328
+ # print(f"Setting up SQS queue '{queue_name}' with policy:\n{policy_json}")
329
+ try:
330
+ queue = self.sqs_resource.get_queue_by_name(QueueName=queue_name)
331
+ self.sqs_client.set_queue_attributes(
332
+ QueueUrl=queue.url,
333
+ Attributes={"Policy": policy_json, "VisibilityTimeout": "900"},
334
+ )
335
+ except self.sqs_client.exceptions.QueueDoesNotExist:
336
+ queue = self.sqs_resource.create_queue(
337
+ QueueName=queue_name,
338
+ Attributes={"Policy": policy_json, "VisibilityTimeout": "900"},
339
+ )
340
+
341
+ try:
342
+ self.lambda_client.create_event_source_mapping(
343
+ EventSourceArn=f"arn:aws:sqs:{region}:{account_id}:{queue_name}",
344
+ FunctionName=queue_handler["FunctionName"],
345
+ Enabled=True,
346
+ BatchSize=1,
347
+ )
348
+ except self.lambda_client.exceptions.ResourceConflictException:
349
+ pass
350
+
351
+ if use_reserved_concurrency:
352
+ reserved = 10 if branch == "production" else 3
353
+ try:
354
+ self.lambda_client.put_function_concurrency(
355
+ FunctionName=queue_handler["FunctionName"],
356
+ ReservedConcurrentExecutions=reserved,
357
+ )
358
+ except Exception:
359
+ self.lambda_client.delete_function_concurrency(
360
+ FunctionName=queue_handler["FunctionName"]
361
+ )
362
+ else:
363
+ try:
364
+ self.lambda_client.delete_function_concurrency(
365
+ FunctionName=queue_handler["FunctionName"]
366
+ )
367
+ except Exception:
368
+ pass
369
+
370
+ def check_policies(
371
+ self,
372
+ ):
373
+ # Attach a role policy
374
+ response = iam_client.list_attached_role_policies(
375
+ RoleName=function["Role"].split("/")[1]
376
+ )
377
+ has_policy_attached = False
378
+ for policy in response["AttachedPolicies"]:
379
+ if policy["PolicyName"] == "lambda-vpc-execution":
380
+ has_policy_attached = True
381
+ if not has_policy_attached:
382
+ account_id = self.get_account_id()
383
+ iam_client.attach_role_policy(
384
+ PolicyArn=f"arn:aws:iam::{account_id}:policy/lambda-vpc-execution",
385
+ RoleName=function["Role"].split("/")[1],
386
+ )
387
+ time.sleep(15)
388
+ has_policy_attached = False
389
+ for policy in response["AttachedPolicies"]:
390
+ if policy["PolicyName"] == "AmazonSQSFullAccess":
391
+ has_policy_attached = True
392
+ if not has_policy_attached:
393
+ iam_client.attach_role_policy(
394
+ PolicyArn="arn:aws:iam::aws:policy/AmazonSQSFullAccess",
395
+ RoleName=function["Role"].split("/")[1],
396
+ )
397
+
398
+ def sync(self, branch):
399
+ print(
400
+ f"\n🚀 Syncing environment variables for app '{self.app_id}' and environment '{branch}'..."
401
+ )
402
+ env_vars = self.get_merged_env_vars(branch)
403
+
404
+ print(f"\n🔧 Applying {len(env_vars)} environment variables...")
405
+ for function_name in self.list_lambda_functions_filtered(branch):
406
+ print(f"➡️ Updating Lambda function: {function_name}")
407
+ self.update_lambda_env(function_name, env_vars)
408
+
409
+ print(
410
+ "✅ Environment variables successfully applied to matching Lambda functions.\n"
411
+ )
412
+
413
+
414
+ def main():
415
+ app_id = "d3c209q3ri53mk"
416
+ branch = "demo"
417
+ app = AmplifyProject(app_id)
418
+ print(app.list_backend_branches())
419
+
420
+
421
+ if __name__ == "__main__":
422
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.78
3
+ Version: 0.0.80
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Paul Perez <pperez@codeclubs.org>
6
6
  Project-URL: Homepage, https://codeclubs.org/projects/velocity
@@ -14,6 +14,7 @@ Requires-Dist: requests
14
14
  Requires-Dist: jinja2
15
15
  Requires-Dist: xlrd
16
16
  Requires-Dist: openpyxl
17
+ Requires-Dist: sqlparse
17
18
  Provides-Extra: mysql
18
19
  Requires-Dist: mysql-connector-python; extra == "mysql"
19
20
  Provides-Extra: sqlserver
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/velocity/__init__.py
5
5
  src/velocity/aws/__init__.py
6
+ src/velocity/aws/amplify.py
6
7
  src/velocity/aws/handlers/__init__.py
7
8
  src/velocity/aws/handlers/context.py
8
9
  src/velocity/aws/handlers/lambda_handler.py
@@ -2,6 +2,7 @@ requests
2
2
  jinja2
3
3
  xlrd
4
4
  openpyxl
5
+ sqlparse
5
6
 
6
7
  [mysql]
7
8
  mysql-connector-python