pycfmodel 2.0.0__tar.gz → 2.0.1__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.
- {pycfmodel-2.0.0/pycfmodel.egg-info → pycfmodel-2.0.1}/PKG-INFO +1 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/cloudformation_actions.py +7 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/autoscaling_auto_scaling_group.py +16 -2
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/cloudwatch_alarm.py +1 -12
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/dynamodb_table.py +23 -17
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/elbv2_target_group.py +2 -13
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_group.py +1 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_user.py +1 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/s3_bucket.py +15 -3
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/sns_topic.py +7 -12
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/sqs_queue.py +3 -12
- {pycfmodel-2.0.0 → pycfmodel-2.0.1/pycfmodel.egg-info}/PKG-INFO +1 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/scripts/generate_resource_from_schema.py +137 -35
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_autoscaling_auto_scaling_group.py +35 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_elbv2_target_group.py +2 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_s3_bucket.py +51 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_sns_topic.py +2 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_sqs_queue.py +2 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_cf_model.py +0 -1
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.flake8 +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/dependabot.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/pull_request_template.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/release-drafter.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/copilot-setup-steps.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/lint-and-test.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/pypi-release.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/release-drafter.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/test-docs.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/update-cloudformation-actions.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.github/workflows/zizmor.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.gitignore +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/.readthedocs.yaml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/CHANGELOG.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/CLAUDE.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/CODE_OF_CONDUCT.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/CONTRIBUTING.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/LICENSE.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/Makefile +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/README.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/changelog.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/code_of_conduct.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/contributing.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/index.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/reference/cfmodel.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/reference/properties.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/reference/resources.md +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/docs/stylesheets/extra.css +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/mkdocs.yml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/__init__.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/action_expander.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/constants.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/base.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/cf_model.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/generic.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/parameter.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/ec2_vpc_endpoint_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/elbv2_listener.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/es_domain.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/generic_resource.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_instance_profile.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_managed_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/iam_role.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/kms_key.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/opensearch_domain.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/policy_document.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/property.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/security_group_egress_prop.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/security_group_ingress_prop.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/statement.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/statement_condition.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/tag.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/properties/types.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/resource.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/route53_record_set.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/s3_bucket_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/security_group.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/security_group_egress.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/security_group_ingress.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/sns_subscription.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/sns_topic_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/sqs_queue_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/types.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/wafv2_ip_set.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/types.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/utils.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/resolver.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/utils.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel.egg-info/SOURCES.txt +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel.egg-info/dependency_links.txt +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel.egg-info/requires.txt +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel.egg-info/top_level.txt +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/pyproject.toml +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/scripts/generate_cloudformation_actions_file.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/setup.cfg +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/properties/test_policy_document.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/properties/test_principal.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/properties/test_statement.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/properties/test_statement_condition.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_cloudwatch_alarm.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_dynamodb_table.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_ec2_vpc_endpoint_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_elbv2_listener.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_es_domain.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_generic_resoure.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_group.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_instance_profile.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_managed_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_role.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_iam_user.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_kms_key.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_opensearch_domain.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_rds_security_groups.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_route53_record_set.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_s3_bucket_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_security_group.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_security_group_egress.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_security_group_ingress.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_sns_subscription.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_sns_topic_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_sqs_queue_policy.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/resources/test_wafv2_ip_set.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_action_expander.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_constants.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_generic.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_parameter.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_regexs.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_resolver.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_resource.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_resource_generator.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_types.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/tests/test_utils.py +0 -0
- {pycfmodel-2.0.0 → pycfmodel-2.0.1}/uv.lock +0 -0
|
@@ -9473,6 +9473,10 @@ CLOUDFORMATION_ACTIONS = [
|
|
|
9473
9473
|
"importexport:ListJobs",
|
|
9474
9474
|
"importexport:UpdateJob",
|
|
9475
9475
|
"inspector-scan:ScanSbom",
|
|
9476
|
+
"inspector2-telemetry:NotifyHeartbeat",
|
|
9477
|
+
"inspector2-telemetry:SendTelemetry",
|
|
9478
|
+
"inspector2-telemetry:StartSession",
|
|
9479
|
+
"inspector2-telemetry:StopSession",
|
|
9476
9480
|
"inspector2:AssociateMember",
|
|
9477
9481
|
"inspector2:BatchAssociateCodeSecurityScanConfiguration",
|
|
9478
9482
|
"inspector2:BatchDisassociateCodeSecurityScanConfiguration",
|
|
@@ -10601,11 +10605,13 @@ CLOUDFORMATION_ACTIONS = [
|
|
|
10601
10605
|
"kafka:CreateClusterV2",
|
|
10602
10606
|
"kafka:CreateConfiguration",
|
|
10603
10607
|
"kafka:CreateReplicator",
|
|
10608
|
+
"kafka:CreateTopic",
|
|
10604
10609
|
"kafka:CreateVpcConnection",
|
|
10605
10610
|
"kafka:DeleteCluster",
|
|
10606
10611
|
"kafka:DeleteClusterPolicy",
|
|
10607
10612
|
"kafka:DeleteConfiguration",
|
|
10608
10613
|
"kafka:DeleteReplicator",
|
|
10614
|
+
"kafka:DeleteTopic",
|
|
10609
10615
|
"kafka:DeleteVpcConnection",
|
|
10610
10616
|
"kafka:DescribeCluster",
|
|
10611
10617
|
"kafka:DescribeClusterOperation",
|
|
@@ -10651,6 +10657,7 @@ CLOUDFORMATION_ACTIONS = [
|
|
|
10651
10657
|
"kafka:UpdateReplicationInfo",
|
|
10652
10658
|
"kafka:UpdateSecurity",
|
|
10653
10659
|
"kafka:UpdateStorage",
|
|
10660
|
+
"kafka:UpdateTopic",
|
|
10654
10661
|
"kafkaconnect:CreateConnector",
|
|
10655
10662
|
"kafkaconnect:CreateCustomPlugin",
|
|
10656
10663
|
"kafkaconnect:CreateWorkerConfiguration",
|
{pycfmodel-2.0.0 → pycfmodel-2.0.1}/pycfmodel/model/resources/autoscaling_auto_scaling_group.py
RENAMED
|
@@ -403,6 +403,18 @@ class MixedInstancesPolicy(CustomModel):
|
|
|
403
403
|
ResolvableMixedInstancesPolicy = ResolvableModel(MixedInstancesPolicy)
|
|
404
404
|
|
|
405
405
|
|
|
406
|
+
# Pre-resolved list type to avoid class body name shadowing in AutoScalingAutoScalingGroupProperties.
|
|
407
|
+
# The AutoScalingAutoScalingGroupProperties class has a field named "MetricsCollection" (default None) which shadows
|
|
408
|
+
# the MetricsCollection class when Python evaluates List[MetricsCollection] inside the class body.
|
|
409
|
+
_MetricsCollectionList = Resolvable[List[MetricsCollection]]
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# Pre-resolved list type to avoid class body name shadowing in AutoScalingAutoScalingGroupProperties.
|
|
413
|
+
# The AutoScalingAutoScalingGroupProperties class has a field named "NotificationConfiguration" (default None) which shadows
|
|
414
|
+
# the NotificationConfiguration class when Python evaluates List[NotificationConfiguration] inside the class body.
|
|
415
|
+
_NotificationConfigurationList = Resolvable[List[NotificationConfiguration]]
|
|
416
|
+
|
|
417
|
+
|
|
406
418
|
class AutoScalingAutoScalingGroupProperties(CustomModel):
|
|
407
419
|
"""
|
|
408
420
|
Properties for AWS::AutoScaling::AutoScalingGroup.
|
|
@@ -419,6 +431,7 @@ class AutoScalingAutoScalingGroupProperties(CustomModel):
|
|
|
419
431
|
- Cooldown: *Only needed if you use simple scaling policies.*
|
|
420
432
|
The amount of time, in secon...
|
|
421
433
|
- DefaultInstanceWarmup: The amount of time, in seconds, until a new instance is considered to have finis...
|
|
434
|
+
- DeletionProtection:
|
|
422
435
|
- DesiredCapacity: The desired capacity is the initial capacity of the Auto Scaling group at the ti...
|
|
423
436
|
- DesiredCapacityType: The unit of measurement for the value specified for desired capacity. Amazon EC2...
|
|
424
437
|
- HealthCheckGracePeriod: The amount of time, in seconds, that Amazon EC2 Auto Scaling waits before checki...
|
|
@@ -465,6 +478,7 @@ class AutoScalingAutoScalingGroupProperties(CustomModel):
|
|
|
465
478
|
Context: Optional[ResolvableStr] = None
|
|
466
479
|
Cooldown: Optional[ResolvableStr] = None
|
|
467
480
|
DefaultInstanceWarmup: Optional[ResolvableInt] = None
|
|
481
|
+
DeletionProtection: Optional[ResolvableStr] = None
|
|
468
482
|
DesiredCapacity: Optional[ResolvableStr] = None
|
|
469
483
|
DesiredCapacityType: Optional[ResolvableStr] = None
|
|
470
484
|
HealthCheckGracePeriod: Optional[ResolvableInt] = None
|
|
@@ -477,11 +491,11 @@ class AutoScalingAutoScalingGroupProperties(CustomModel):
|
|
|
477
491
|
LifecycleHookSpecificationList: Optional[Resolvable[List[LifecycleHookSpecification]]] = None
|
|
478
492
|
LoadBalancerNames: Optional[Resolvable[List[ResolvableStr]]] = None
|
|
479
493
|
MaxInstanceLifetime: Optional[ResolvableInt] = None
|
|
480
|
-
MetricsCollection: Optional[
|
|
494
|
+
MetricsCollection: Optional[_MetricsCollectionList] = None
|
|
481
495
|
MixedInstancesPolicy: Optional[ResolvableMixedInstancesPolicy] = None
|
|
482
496
|
NewInstancesProtectedFromScaleIn: Optional[ResolvableBool] = None
|
|
483
497
|
NotificationConfiguration: Optional[ResolvableNotificationConfiguration] = None
|
|
484
|
-
NotificationConfigurations: Optional[
|
|
498
|
+
NotificationConfigurations: Optional[_NotificationConfigurationList] = None
|
|
485
499
|
PlacementGroup: Optional[ResolvableStr] = None
|
|
486
500
|
ServiceLinkedRoleARN: Optional[ResolvableStr] = None
|
|
487
501
|
SkipZonalShiftValidation: Optional[ResolvableBool] = None
|
|
@@ -7,6 +7,7 @@ Auto-generated from AWS CloudFormation schema for AWS::CloudWatch::Alarm.
|
|
|
7
7
|
from typing import List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pycfmodel.model.base import CustomModel
|
|
10
|
+
from pycfmodel.model.resources.properties.tag import Tag
|
|
10
11
|
from pycfmodel.model.resources.resource import Resource
|
|
11
12
|
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableInt, ResolvableModel, ResolvableStr
|
|
12
13
|
|
|
@@ -67,18 +68,6 @@ class MetricDataQuery(CustomModel):
|
|
|
67
68
|
ResolvableMetricDataQuery = ResolvableModel(MetricDataQuery)
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
class Tag(CustomModel):
|
|
71
|
-
"""
|
|
72
|
-
One of the key-value pairs associated with the alarm.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
Key: ResolvableStr
|
|
76
|
-
Value: ResolvableStr
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
ResolvableTag = ResolvableModel(Tag)
|
|
80
|
-
|
|
81
|
-
|
|
82
71
|
class CloudWatchAlarmProperties(CustomModel):
|
|
83
72
|
"""
|
|
84
73
|
Properties for AWS::CloudWatch::Alarm.
|
|
@@ -7,7 +7,7 @@ Auto-generated from AWS CloudFormation schema for AWS::DynamoDB::Table.
|
|
|
7
7
|
from typing import List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pycfmodel.model.base import CustomModel
|
|
10
|
-
from pycfmodel.model.
|
|
10
|
+
from pycfmodel.model.resources.properties.tag import Tag
|
|
11
11
|
from pycfmodel.model.resources.resource import Resource
|
|
12
12
|
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableInt, ResolvableModel, ResolvableStr
|
|
13
13
|
|
|
@@ -119,13 +119,19 @@ class Projection(CustomModel):
|
|
|
119
119
|
ResolvableProjection = ResolvableModel(Projection)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
# Pre-resolved list type to avoid class body name shadowing in LocalSecondaryIndex.
|
|
123
|
+
# The LocalSecondaryIndex class has a field named "KeySchema" (default None) which shadows
|
|
124
|
+
# the KeySchema class when Python evaluates List[KeySchema] inside the class body.
|
|
125
|
+
_KeySchemaList = Resolvable[List[KeySchema]]
|
|
126
|
+
|
|
127
|
+
|
|
122
128
|
class LocalSecondaryIndex(CustomModel):
|
|
123
129
|
"""
|
|
124
130
|
Represents the properties of a local secondary index.
|
|
125
131
|
"""
|
|
126
132
|
|
|
127
133
|
IndexName: ResolvableStr
|
|
128
|
-
KeySchema:
|
|
134
|
+
KeySchema: _KeySchemaList
|
|
129
135
|
Projection: ResolvableProjection
|
|
130
136
|
|
|
131
137
|
|
|
@@ -149,7 +155,7 @@ class ResourcePolicy(CustomModel):
|
|
|
149
155
|
Creates or updates a resource-based policy document that contains the permissions for DDB resources, such as a table, its indexes, and stream.
|
|
150
156
|
"""
|
|
151
157
|
|
|
152
|
-
PolicyDocument:
|
|
158
|
+
PolicyDocument: Resolvable[dict]
|
|
153
159
|
|
|
154
160
|
|
|
155
161
|
ResolvableResourcePolicy = ResolvableModel(ResourcePolicy)
|
|
@@ -207,18 +213,6 @@ class StreamSpecification(CustomModel):
|
|
|
207
213
|
ResolvableStreamSpecification = ResolvableModel(StreamSpecification)
|
|
208
214
|
|
|
209
215
|
|
|
210
|
-
class Tag(CustomModel):
|
|
211
|
-
"""
|
|
212
|
-
Describes a tag.
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
Key: ResolvableStr
|
|
216
|
-
Value: ResolvableStr
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
ResolvableTag = ResolvableModel(Tag)
|
|
220
|
-
|
|
221
|
-
|
|
222
216
|
class TimeToLiveSpecification(CustomModel):
|
|
223
217
|
"""
|
|
224
218
|
Represents the settings used to enable or disable Time to Live (TTL) for the specified table.
|
|
@@ -243,13 +237,19 @@ class WarmThroughput(CustomModel):
|
|
|
243
237
|
ResolvableWarmThroughput = ResolvableModel(WarmThroughput)
|
|
244
238
|
|
|
245
239
|
|
|
240
|
+
# Pre-resolved list type to avoid class body name shadowing in GlobalSecondaryIndex.
|
|
241
|
+
# The GlobalSecondaryIndex class has a field named "KeySchema" (default None) which shadows
|
|
242
|
+
# the KeySchema class when Python evaluates List[KeySchema] inside the class body.
|
|
243
|
+
_KeySchemaList = Resolvable[List[KeySchema]]
|
|
244
|
+
|
|
245
|
+
|
|
246
246
|
class GlobalSecondaryIndex(CustomModel):
|
|
247
247
|
"""
|
|
248
248
|
Represents the properties of a global secondary index.
|
|
249
249
|
"""
|
|
250
250
|
|
|
251
251
|
IndexName: ResolvableStr
|
|
252
|
-
KeySchema:
|
|
252
|
+
KeySchema: _KeySchemaList
|
|
253
253
|
Projection: ResolvableProjection
|
|
254
254
|
ContributorInsightsSpecification: Optional[ResolvableContributorInsightsSpecification] = None
|
|
255
255
|
OnDemandThroughput: Optional[ResolvableOnDemandThroughput] = None
|
|
@@ -260,6 +260,12 @@ class GlobalSecondaryIndex(CustomModel):
|
|
|
260
260
|
ResolvableGlobalSecondaryIndex = ResolvableModel(GlobalSecondaryIndex)
|
|
261
261
|
|
|
262
262
|
|
|
263
|
+
# Pre-resolved list type to avoid class body name shadowing in DynamoDBTableProperties.
|
|
264
|
+
# The DynamoDBTableProperties class has a field named "KeySchema" (default None) which shadows
|
|
265
|
+
# the KeySchema class when Python evaluates List[KeySchema] inside the class body.
|
|
266
|
+
_KeySchemaList = Resolvable[List[KeySchema]]
|
|
267
|
+
|
|
268
|
+
|
|
263
269
|
class DynamoDBTableProperties(CustomModel):
|
|
264
270
|
"""
|
|
265
271
|
Properties for AWS::DynamoDB::Table.
|
|
@@ -293,7 +299,7 @@ class DynamoDBTableProperties(CustomModel):
|
|
|
293
299
|
More info at [AWS Docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html)
|
|
294
300
|
"""
|
|
295
301
|
|
|
296
|
-
KeySchema:
|
|
302
|
+
KeySchema: _KeySchemaList
|
|
297
303
|
AttributeDefinitions: Optional[Resolvable[List[AttributeDefinition]]] = None
|
|
298
304
|
BillingMode: Optional[ResolvableStr] = None
|
|
299
305
|
ContributorInsightsSpecification: Optional[ResolvableContributorInsightsSpecification] = None
|
|
@@ -7,6 +7,7 @@ Auto-generated from AWS CloudFormation schema for AWS::ElasticLoadBalancingV2::T
|
|
|
7
7
|
from typing import List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pycfmodel.model.base import CustomModel
|
|
10
|
+
from pycfmodel.model.resources.properties.tag import Tag
|
|
10
11
|
from pycfmodel.model.resources.resource import Resource
|
|
11
12
|
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableInt, ResolvableModel, ResolvableStr
|
|
12
13
|
|
|
@@ -23,18 +24,6 @@ class Matcher(CustomModel):
|
|
|
23
24
|
ResolvableMatcher = ResolvableModel(Matcher)
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class Tag(CustomModel):
|
|
27
|
-
"""
|
|
28
|
-
Tag configuration.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
Key: ResolvableStr
|
|
32
|
-
Value: ResolvableStr
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ResolvableTag = ResolvableModel(Tag)
|
|
36
|
-
|
|
37
|
-
|
|
38
27
|
class TargetDescription(CustomModel):
|
|
39
28
|
"""
|
|
40
29
|
TargetDescription configuration.
|
|
@@ -125,4 +114,4 @@ class ELBv2TargetGroup(Resource):
|
|
|
125
114
|
"""
|
|
126
115
|
|
|
127
116
|
Type: Literal["AWS::ElasticLoadBalancingV2::TargetGroup"]
|
|
128
|
-
Properties:
|
|
117
|
+
Properties: Resolvable[ELBv2TargetGroupProperties] = ELBv2TargetGroupProperties()
|
|
@@ -35,7 +35,7 @@ class IAMGroup(Resource):
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
Type: Literal["AWS::IAM::Group"]
|
|
38
|
-
Properties:
|
|
38
|
+
Properties: Resolvable[IAMGroupProperties] = IAMGroupProperties()
|
|
39
39
|
|
|
40
40
|
@property
|
|
41
41
|
def policy_documents(self) -> List[OptionallyNamedPolicyDocument]:
|
|
@@ -59,7 +59,7 @@ class IAMUser(Resource):
|
|
|
59
59
|
"""
|
|
60
60
|
|
|
61
61
|
Type: Literal["AWS::IAM::User"]
|
|
62
|
-
Properties:
|
|
62
|
+
Properties: Resolvable[IAMUserProperties] = IAMUserProperties()
|
|
63
63
|
|
|
64
64
|
def has_hardcoded_credentials(self) -> bool:
|
|
65
65
|
"""Returns True if login profile password contains a hardcoded string, otherwise False."""
|
|
@@ -814,6 +814,18 @@ class Transition(CustomModel):
|
|
|
814
814
|
ResolvableTransition = ResolvableModel(Transition)
|
|
815
815
|
|
|
816
816
|
|
|
817
|
+
# Pre-resolved list type to avoid class body name shadowing in Rule.
|
|
818
|
+
# The Rule class has a field named "NoncurrentVersionTransition" (default None) which shadows
|
|
819
|
+
# the NoncurrentVersionTransition class when Python evaluates List[NoncurrentVersionTransition] inside the class body.
|
|
820
|
+
_NoncurrentVersionTransitionList = Resolvable[List[NoncurrentVersionTransition]]
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
# Pre-resolved list type to avoid class body name shadowing in Rule.
|
|
824
|
+
# The Rule class has a field named "Transition" (default None) which shadows
|
|
825
|
+
# the Transition class when Python evaluates List[Transition] inside the class body.
|
|
826
|
+
_TransitionList = Resolvable[List[Transition]]
|
|
827
|
+
|
|
828
|
+
|
|
817
829
|
class Rule(CustomModel):
|
|
818
830
|
"""
|
|
819
831
|
Specifies lifecycle rules for an Amazon S3 bucket.
|
|
@@ -828,13 +840,13 @@ class Rule(CustomModel):
|
|
|
828
840
|
NoncurrentVersionExpiration: Optional[ResolvableNoncurrentVersionExpiration] = None
|
|
829
841
|
NoncurrentVersionExpirationInDays: Optional[ResolvableInt] = None
|
|
830
842
|
NoncurrentVersionTransition: Optional[ResolvableNoncurrentVersionTransition] = None
|
|
831
|
-
NoncurrentVersionTransitions: Optional[
|
|
843
|
+
NoncurrentVersionTransitions: Optional[_NoncurrentVersionTransitionList] = None
|
|
832
844
|
ObjectSizeGreaterThan: Optional[ResolvableStr] = None
|
|
833
845
|
ObjectSizeLessThan: Optional[ResolvableStr] = None
|
|
834
846
|
Prefix: Optional[ResolvableStr] = None
|
|
835
847
|
TagFilters: Optional[Resolvable[List[TagFilter]]] = None
|
|
836
848
|
Transition: Optional[ResolvableTransition] = None
|
|
837
|
-
Transitions: Optional[
|
|
849
|
+
Transitions: Optional[_TransitionList] = None
|
|
838
850
|
|
|
839
851
|
|
|
840
852
|
ResolvableRule = ResolvableModel(Rule)
|
|
@@ -923,4 +935,4 @@ class S3Bucket(Resource):
|
|
|
923
935
|
"""
|
|
924
936
|
|
|
925
937
|
Type: Literal["AWS::S3::Bucket"]
|
|
926
|
-
Properties:
|
|
938
|
+
Properties: Resolvable[S3BucketProperties] = S3BucketProperties()
|
|
@@ -7,6 +7,7 @@ Auto-generated from AWS CloudFormation schema for AWS::SNS::Topic.
|
|
|
7
7
|
from typing import List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pycfmodel.model.base import CustomModel
|
|
10
|
+
from pycfmodel.model.resources.properties.tag import Tag
|
|
10
11
|
from pycfmodel.model.resources.resource import Resource
|
|
11
12
|
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableModel, ResolvableStr
|
|
12
13
|
|
|
@@ -37,16 +38,10 @@ class Subscription(CustomModel):
|
|
|
37
38
|
ResolvableSubscription = ResolvableModel(Subscription)
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
class
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Key: ResolvableStr
|
|
46
|
-
Value: ResolvableStr
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
ResolvableTag = ResolvableModel(Tag)
|
|
41
|
+
# Pre-resolved list type to avoid class body name shadowing in SNSTopicProperties.
|
|
42
|
+
# The SNSTopicProperties class has a field named "Subscription" (default None) which shadows
|
|
43
|
+
# the Subscription class when Python evaluates List[Subscription] inside the class body.
|
|
44
|
+
_SubscriptionList = Resolvable[List[Subscription]]
|
|
50
45
|
|
|
51
46
|
|
|
52
47
|
class SNSTopicProperties(CustomModel):
|
|
@@ -85,7 +80,7 @@ class SNSTopicProperties(CustomModel):
|
|
|
85
80
|
FifoTopic: Optional[ResolvableBool] = None
|
|
86
81
|
KmsMasterKeyId: Optional[ResolvableStr] = None
|
|
87
82
|
SignatureVersion: Optional[ResolvableStr] = None
|
|
88
|
-
Subscription: Optional[
|
|
83
|
+
Subscription: Optional[_SubscriptionList] = None
|
|
89
84
|
Tags: Optional[Resolvable[List[Tag]]] = None
|
|
90
85
|
TopicName: Optional[ResolvableStr] = None
|
|
91
86
|
TracingConfig: Optional[ResolvableStr] = None
|
|
@@ -103,4 +98,4 @@ class SNSTopic(Resource):
|
|
|
103
98
|
"""
|
|
104
99
|
|
|
105
100
|
Type: Literal["AWS::SNS::Topic"]
|
|
106
|
-
Properties:
|
|
101
|
+
Properties: Resolvable[SNSTopicProperties] = SNSTopicProperties()
|
|
@@ -7,18 +7,9 @@ Auto-generated from AWS CloudFormation schema for AWS::SQS::Queue.
|
|
|
7
7
|
from typing import List, Literal, Optional
|
|
8
8
|
|
|
9
9
|
from pycfmodel.model.base import CustomModel
|
|
10
|
+
from pycfmodel.model.resources.properties.tag import Tag
|
|
10
11
|
from pycfmodel.model.resources.resource import Resource
|
|
11
|
-
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableInt,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Tag(CustomModel):
|
|
15
|
-
""" """
|
|
16
|
-
|
|
17
|
-
Key: ResolvableStr
|
|
18
|
-
Value: ResolvableStr
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ResolvableTag = ResolvableModel(Tag)
|
|
12
|
+
from pycfmodel.model.types import Resolvable, ResolvableBool, ResolvableInt, ResolvableStr
|
|
22
13
|
|
|
23
14
|
|
|
24
15
|
class SQSQueueProperties(CustomModel):
|
|
@@ -77,4 +68,4 @@ class SQSQueue(Resource):
|
|
|
77
68
|
"""
|
|
78
69
|
|
|
79
70
|
Type: Literal["AWS::SQS::Queue"]
|
|
80
|
-
Properties:
|
|
71
|
+
Properties: Resolvable[SQSQueueProperties] = SQSQueueProperties()
|
|
@@ -279,37 +279,64 @@ class CodeGenerator:
|
|
|
279
279
|
# Handle anyOf/oneOf
|
|
280
280
|
if "anyOf" in prop_schema or "oneOf" in prop_schema:
|
|
281
281
|
variants = prop_schema.get("anyOf") or prop_schema.get("oneOf", [])
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
282
|
+
|
|
283
|
+
# Skip variants that are only relationshipRef metadata (not real type info).
|
|
284
|
+
# If all variants are just relationshipRef, fall through to the "type" field.
|
|
285
|
+
typed_variants = [v for v in variants if not (set(v.keys()) <= {"relationshipRef"})]
|
|
286
|
+
|
|
287
|
+
if typed_variants:
|
|
288
|
+
# Check if any variant is an array
|
|
289
|
+
for variant in typed_variants:
|
|
290
|
+
if variant.get("type") == "array":
|
|
291
|
+
items = variant.get("items", {})
|
|
292
|
+
item_type = self._get_item_type(items)
|
|
293
|
+
self.imports.add("from typing import List")
|
|
294
|
+
return f"Resolvable[List[{item_type}]]"
|
|
295
|
+
# Check for $ref in variants
|
|
296
|
+
for variant in typed_variants:
|
|
297
|
+
if "$ref" in variant:
|
|
298
|
+
ref = variant["$ref"]
|
|
299
|
+
if ref.startswith("#/definitions/"):
|
|
300
|
+
def_name = ref.split("/")[-1]
|
|
301
|
+
definition = self.definitions.get(def_name, {})
|
|
302
|
+
if definition.get("type") == "object" or definition.get("properties"):
|
|
303
|
+
# Generate a nested model class and use ResolvableModel
|
|
304
|
+
self.needed_definitions.add(def_name)
|
|
305
|
+
self.imports.add("from pycfmodel.model.types import ResolvableModel")
|
|
306
|
+
return f"Resolvable{def_name}"
|
|
307
|
+
else:
|
|
308
|
+
# Fall back to inline handling
|
|
309
|
+
return self.get_type_for_schema(variant, is_required, context)
|
|
310
|
+
return "Resolvable[dict]"
|
|
311
|
+
# All variants were relationshipRef only — fall through to use the "type" field
|
|
305
312
|
|
|
306
313
|
json_type = prop_schema.get("type")
|
|
307
314
|
|
|
308
315
|
if json_type is None:
|
|
309
316
|
return "Resolvable[dict]"
|
|
310
317
|
|
|
311
|
-
# Handle multiple types (e.g., ["string", "object"])
|
|
318
|
+
# Handle multiple types (e.g., ["string", "object"]).
|
|
319
|
+
# If "object" is present, use Resolvable[dict] since it accepts both dicts and strings.
|
|
320
|
+
# Otherwise, use the first recognized primitive type.
|
|
312
321
|
if isinstance(json_type, list):
|
|
322
|
+
if "object" in json_type:
|
|
323
|
+
return "Resolvable[dict]"
|
|
324
|
+
type_map = {
|
|
325
|
+
"string": "ResolvableStr",
|
|
326
|
+
"integer": "ResolvableInt",
|
|
327
|
+
"number": "ResolvableInt",
|
|
328
|
+
"boolean": "ResolvableBool",
|
|
329
|
+
}
|
|
330
|
+
for t in json_type:
|
|
331
|
+
if t in type_map:
|
|
332
|
+
type_str = type_map[t]
|
|
333
|
+
if type_str == "ResolvableStr":
|
|
334
|
+
self.imports.add("from pycfmodel.model.types import ResolvableStr")
|
|
335
|
+
elif type_str == "ResolvableInt":
|
|
336
|
+
self.imports.add("from pycfmodel.model.types import ResolvableInt")
|
|
337
|
+
elif type_str == "ResolvableBool":
|
|
338
|
+
self.imports.add("from pycfmodel.model.types import ResolvableBool")
|
|
339
|
+
return type_str
|
|
313
340
|
return "Resolvable[dict]"
|
|
314
341
|
|
|
315
342
|
# Handle array types
|
|
@@ -464,6 +491,59 @@ class CodeGenerator:
|
|
|
464
491
|
return True
|
|
465
492
|
return False
|
|
466
493
|
|
|
494
|
+
def _detect_field_name_shadowing(self, fields: List[Tuple[str, str, str, bool]]) -> Dict[str, str]:
|
|
495
|
+
"""
|
|
496
|
+
Detect fields whose names shadow class names used in List[] annotations of sibling fields.
|
|
497
|
+
|
|
498
|
+
When a class has a field like `Transition: Optional[...] = None` and another field
|
|
499
|
+
`Transitions: Optional[Resolvable[List[Transition]]] = None`, Python evaluates the
|
|
500
|
+
List[Transition] annotation inside the class body where `Transition` has already been
|
|
501
|
+
set to None (the default value), causing List[Transition] to resolve to List[NoneType].
|
|
502
|
+
|
|
503
|
+
Returns a dict mapping class names to their pre-resolved type alias names.
|
|
504
|
+
"""
|
|
505
|
+
field_names = {name for name, _, _, _ in fields}
|
|
506
|
+
aliases = {}
|
|
507
|
+
|
|
508
|
+
for _, type_annotation, _, _ in fields:
|
|
509
|
+
# Find all List[ClassName] patterns in the annotation
|
|
510
|
+
match = re.search(r"Resolvable\[List\[(\w+)\]\]", type_annotation)
|
|
511
|
+
if match:
|
|
512
|
+
class_name = match.group(1)
|
|
513
|
+
if class_name in field_names and class_name not in aliases:
|
|
514
|
+
alias_name = f"_{class_name}List"
|
|
515
|
+
aliases[class_name] = alias_name
|
|
516
|
+
|
|
517
|
+
return aliases
|
|
518
|
+
|
|
519
|
+
def _apply_shadowing_aliases(
|
|
520
|
+
self, fields: List[Tuple[str, str, str, bool]], aliases: Dict[str, str]
|
|
521
|
+
) -> List[Tuple[str, str, str, bool]]:
|
|
522
|
+
"""Replace Resolvable[List[ClassName]] with the pre-resolved alias in field annotations."""
|
|
523
|
+
if not aliases:
|
|
524
|
+
return fields
|
|
525
|
+
|
|
526
|
+
result = []
|
|
527
|
+
for name, type_annotation, default, is_required in fields:
|
|
528
|
+
for class_name, alias_name in aliases.items():
|
|
529
|
+
type_annotation = type_annotation.replace(f"Resolvable[List[{class_name}]]", alias_name)
|
|
530
|
+
result.append((name, type_annotation, default, is_required))
|
|
531
|
+
return result
|
|
532
|
+
|
|
533
|
+
def _generate_shadowing_alias_lines(self, aliases: Dict[str, str], class_name: str) -> List[str]:
|
|
534
|
+
"""Generate module-level type alias lines to avoid class body name shadowing."""
|
|
535
|
+
lines = []
|
|
536
|
+
for original_class, alias_name in sorted(aliases.items()):
|
|
537
|
+
lines.append(f"# Pre-resolved list type to avoid class body name shadowing in {class_name}.")
|
|
538
|
+
lines.append(f'# The {class_name} class has a field named "{original_class}" (default None) which shadows')
|
|
539
|
+
lines.append(
|
|
540
|
+
f"# the {original_class} class when Python evaluates List[{original_class}] inside the class body."
|
|
541
|
+
)
|
|
542
|
+
lines.append(f"{alias_name} = Resolvable[List[{original_class}]]")
|
|
543
|
+
lines.append("")
|
|
544
|
+
lines.append("")
|
|
545
|
+
return lines
|
|
546
|
+
|
|
467
547
|
def generate_definition_class(self, def_name: str) -> List[str]:
|
|
468
548
|
"""Generate a class for a schema definition."""
|
|
469
549
|
if def_name in self.generated_definitions:
|
|
@@ -482,15 +562,6 @@ class CodeGenerator:
|
|
|
482
562
|
required = set(definition.get("required", []))
|
|
483
563
|
desc = definition.get("description", f"{def_name} configuration.")
|
|
484
564
|
|
|
485
|
-
lines = []
|
|
486
|
-
lines.append(f"class {def_name}(CustomModel):")
|
|
487
|
-
lines.append(' """')
|
|
488
|
-
# Truncate description to first sentence or 200 chars
|
|
489
|
-
short_desc = desc.split(".")[0] + "." if "." in desc else desc[:200]
|
|
490
|
-
lines.append(f" {short_desc}")
|
|
491
|
-
lines.append(' """')
|
|
492
|
-
lines.append("")
|
|
493
|
-
|
|
494
565
|
# Generate fields
|
|
495
566
|
fields = []
|
|
496
567
|
for prop_name, prop_schema in sorted(props.items()):
|
|
@@ -504,6 +575,23 @@ class CodeGenerator:
|
|
|
504
575
|
default = "" if is_required else " = None"
|
|
505
576
|
fields.append((prop_name, type_annotation, default, is_required))
|
|
506
577
|
|
|
578
|
+
# Detect and fix field name shadowing in List[] annotations
|
|
579
|
+
aliases = self._detect_field_name_shadowing(fields)
|
|
580
|
+
fields = self._apply_shadowing_aliases(fields, aliases)
|
|
581
|
+
|
|
582
|
+
lines = []
|
|
583
|
+
|
|
584
|
+
# Emit module-level type aliases before the class to avoid shadowing
|
|
585
|
+
lines.extend(self._generate_shadowing_alias_lines(aliases, def_name))
|
|
586
|
+
|
|
587
|
+
lines.append(f"class {def_name}(CustomModel):")
|
|
588
|
+
lines.append(' """')
|
|
589
|
+
# Truncate description to first sentence or 200 chars
|
|
590
|
+
short_desc = desc.split(".")[0] + "." if "." in desc else desc[:200]
|
|
591
|
+
lines.append(f" {short_desc}")
|
|
592
|
+
lines.append(' """')
|
|
593
|
+
lines.append("")
|
|
594
|
+
|
|
507
595
|
# Sort: required first, then optional
|
|
508
596
|
required_fields = [(n, t, d) for n, t, d, r in fields if r]
|
|
509
597
|
optional_fields = [(n, t, d) for n, t, d, r in fields if not r]
|
|
@@ -591,6 +679,19 @@ class CodeGenerator:
|
|
|
591
679
|
# Each class is immediately followed by its ResolvableX type alias
|
|
592
680
|
lines.extend(definition_code_lines)
|
|
593
681
|
|
|
682
|
+
# Detect and fix field name shadowing in Properties class List[] annotations
|
|
683
|
+
props_fields_for_shadowing = [(n, t, d, r) for n, t, d, _, r in property_fields]
|
|
684
|
+
props_aliases = self._detect_field_name_shadowing(props_fields_for_shadowing)
|
|
685
|
+
if props_aliases:
|
|
686
|
+
property_fields = [
|
|
687
|
+
(n, t_new, d, desc, r)
|
|
688
|
+
for (n, _, d, desc, r), (_, t_new, _, _) in zip(
|
|
689
|
+
property_fields,
|
|
690
|
+
self._apply_shadowing_aliases(props_fields_for_shadowing, props_aliases),
|
|
691
|
+
)
|
|
692
|
+
]
|
|
693
|
+
lines.extend(self._generate_shadowing_alias_lines(props_aliases, self.props_class_name))
|
|
694
|
+
|
|
594
695
|
# Properties class
|
|
595
696
|
lines.append(f"class {self.props_class_name}(CustomModel):")
|
|
596
697
|
lines.append(' """')
|
|
@@ -641,12 +742,13 @@ class CodeGenerator:
|
|
|
641
742
|
lines.append("")
|
|
642
743
|
lines.append(f' Type: Literal["{self.resource_type}"]')
|
|
643
744
|
|
|
644
|
-
#
|
|
745
|
+
# When no properties are required, default to an empty properties object
|
|
746
|
+
# so that accessing Properties.SomeField returns None instead of raising AttributeError.
|
|
645
747
|
has_required_props = any(r for _, _, _, _, r in property_fields)
|
|
646
748
|
if has_required_props:
|
|
647
749
|
lines.append(f" Properties: Resolvable[{self.props_class_name}]")
|
|
648
750
|
else:
|
|
649
|
-
lines.append(f" Properties:
|
|
751
|
+
lines.append(f" Properties: Resolvable[{self.props_class_name}] = {self.props_class_name}()")
|
|
650
752
|
|
|
651
753
|
lines.append("")
|
|
652
754
|
|
|
@@ -174,6 +174,41 @@ def test_autoscaling_group_with_notification():
|
|
|
174
174
|
assert asg.Properties.NotificationConfiguration.TopicARN == "arn:aws:sns:us-east-1:123456789012:my-topic"
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def test_autoscaling_group_with_notification_configurations():
|
|
178
|
+
"""
|
|
179
|
+
Regression test: NotificationConfigurations must be parsed correctly as
|
|
180
|
+
List[NotificationConfiguration], not as List[NoneType] due to field name
|
|
181
|
+
shadowing from the singular NotificationConfiguration field in the class body.
|
|
182
|
+
"""
|
|
183
|
+
asg = AutoScalingAutoScalingGroup(
|
|
184
|
+
**{
|
|
185
|
+
"Type": "AWS::AutoScaling::AutoScalingGroup",
|
|
186
|
+
"Properties": {
|
|
187
|
+
"MinSize": "1",
|
|
188
|
+
"MaxSize": "5",
|
|
189
|
+
"LaunchTemplate": {
|
|
190
|
+
"LaunchTemplateId": "lt-123",
|
|
191
|
+
"Version": "$Latest",
|
|
192
|
+
},
|
|
193
|
+
"VPCZoneIdentifier": ["subnet-123"],
|
|
194
|
+
"NotificationConfigurations": [
|
|
195
|
+
{
|
|
196
|
+
"TopicARN": "arn:aws:sns:us-east-1:123456789012:launch-topic",
|
|
197
|
+
"NotificationTypes": ["autoscaling:EC2_INSTANCE_LAUNCH"],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"TopicARN": "arn:aws:sns:us-east-1:123456789012:terminate-topic",
|
|
201
|
+
"NotificationTypes": ["autoscaling:EC2_INSTANCE_TERMINATE"],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
assert len(asg.Properties.NotificationConfigurations) == 2
|
|
208
|
+
assert asg.Properties.NotificationConfigurations[0].TopicARN == "arn:aws:sns:us-east-1:123456789012:launch-topic"
|
|
209
|
+
assert asg.Properties.NotificationConfigurations[1].TopicARN == "arn:aws:sns:us-east-1:123456789012:terminate-topic"
|
|
210
|
+
|
|
211
|
+
|
|
177
212
|
def test_autoscaling_group_with_tags():
|
|
178
213
|
asg = AutoScalingAutoScalingGroup(
|
|
179
214
|
**{
|