Repository: Skyscanner/pycfmodel (language: python)

Issue:
Able to parse resources without Properties
## 0.18.1
### Fixes
- `Properties` can now be optional for Generic. See [PR #96](https://github.com/Skyscanner/pycfmodel/pull/96)
### Updates
- Update `CLOUDFORMATION_ACTIONS`.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 045f218f..f89d5988 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 # Change Log
 All notable changes to this project will be documented in this file.
 
+## 0.18.1
+### Fixes
+- `Properties` can now be optional for Generic. See [PR #96](https://github.com/Skyscanner/pycfmodel/pull/96)
+### Updates
+- Update `CLOUDFORMATION_ACTIONS`.
+
 ## 0.18.0 - [2022-04-01]
 ### Fixes
 - Removes `smart_union` flag from Generic. See [PR #95](https://github.com/Skyscanner/pycfmodel/pull/95)
diff --git a/pycfmodel/cloudformation_actions.py b/pycfmodel/cloudformation_actions.py
index d7d162cb..f1f722eb 100644
--- a/pycfmodel/cloudformation_actions.py
+++ b/pycfmodel/cloudformation_actions.py
@@ -2524,6 +2524,8 @@
     "connect:AssociateQueueQuickConnects",
     "connect:AssociateRoutingProfileQueues",
     "connect:AssociateSecurityKey",
+    "connect:BatchAssociateAnalyticsDataSet",
+    "connect:BatchDisassociateAnalyticsDataSet",
     "connect:CreateAgentStatus",
     "connect:CreateContactFlow",
     "connect:CreateContactFlowModule",
@@ -4004,6 +4006,7 @@
     "ecr:BatchDeleteImage",
     "ecr:BatchGetImage",
     "ecr:BatchGetRepositoryScanningConfiguration",
+    "ecr:BatchImportUpstreamImage",
     "ecr:CompleteLayerUpload",
     "ecr:CreatePullThroughCacheRule",
     "ecr:CreateRepository",
@@ -10167,8 +10170,10 @@
     "securityhub:AcceptInvitation",
     "securityhub:BatchDisableStandards",
     "securityhub:BatchEnableStandards",
+    "securityhub:BatchGetStandardsControlAssociations",
     "securityhub:BatchImportFindings",
     "securityhub:BatchUpdateFindings",
+    "securityhub:BatchUpdateStandardsControlAssociations",
     "securityhub:CreateActionTarget",
     "securityhub:CreateFindingAggregator",
     "securityhub:CreateInsight",
@@ -10216,6 +10221,7 @@
     "securityhub:ListInvitations",
     "securityhub:ListMembers",
     "securityhub:ListOrganizationAdminAccounts",
+    "securityhub:ListSecurityControlDefinitions",
     "securityhub:ListTagsForResource",
     "securityhub:SendFindingEvents",
     "securityhub:SendInsightEvents",
@@ -12090,6 +12096,7 @@
     "workspaces:CreateUpdatedWorkspaceImage",
     "workspaces:CreateWorkspaceBundle",
     "workspaces:CreateWorkspaces",
+    "workspaces:DeleteClientBranding",
     "workspaces:DeleteConnectClientAddIn",
     "workspaces:DeleteConnectionAlias",
     "workspaces:DeleteIpGroup",
@@ -12099,6 +12106,7 @@
     "workspaces:DeregisterWorkspaceDirectory",
     "workspaces:DescribeAccount",
     "workspaces:DescribeAccountModifications",
+    "workspaces:DescribeClientBranding",
     "workspaces:DescribeClientProperties",
     "workspaces:DescribeConnectClientAddIns",
     "workspaces:DescribeConnectionAliasPermissions",
@@ -12114,6 +12122,7 @@
     "workspaces:DescribeWorkspacesConnectionStatus",
     "workspaces:DisassociateConnectionAlias",
     "workspaces:DisassociateIpGroups",
+    "workspaces:ImportClientBranding",
     "workspaces:ImportWorkspaceImage",
     "workspaces:ListAvailableManagementCidrRanges",
     "workspaces:MigrateWorkspace",
diff --git a/pycfmodel/model/resources/generic_resource.py b/pycfmodel/model/resources/generic_resource.py
index a678997b..f9b4f037 100644
--- a/pycfmodel/model/resources/generic_resource.py
+++ b/pycfmodel/model/resources/generic_resource.py
@@ -1,5 +1,5 @@
 import logging
-from typing import ClassVar
+from typing import ClassVar, Optional
 
 from pydantic import Extra, validator
 
@@ -17,7 +17,7 @@ class GenericResource(Resource):
 
     ALLOW_EXISTING_TYPES: ClassVar[bool] = True
     Type: str
-    Properties: Generic
+    Properties: Optional[Generic] = None
 
     class Config(Resource.Config):
         extra = Extra.allow
diff --git a/setup.py b/setup.py
index 8e44a758..ce0c3b62 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@
 
 setup(
     name="pycfmodel",
-    version="0.18.0",
+    version="0.18.1",
     description="A python model for CloudFormation scripts",
     author="Skyscanner Product Security",
     author_email="security@skyscanner.net",


Test patch:
diff --git a/tests/resources/test_generic_resoure.py b/tests/resources/test_generic_resoure.py
index 92cbc38c..f3514ace 100644
--- a/tests/resources/test_generic_resoure.py
+++ b/tests/resources/test_generic_resoure.py
@@ -38,3 +38,14 @@ def test_generic_resource():
         StatementCondition(StringEquals={"aws:username": "jamesbond"}),
         StatementCondition(IpAddress={"aws:SourceIp": "203.0.113.0/24"}),
     ]
+
+
+def test_parse_generic_resource_without_properties():
+    resource = GenericResource.parse_obj(
+        {
+            "Type": "AWS::SNS::Topic",
+        }
+    )
+    assert isinstance(resource, GenericResource)
+    assert resource.Properties is None
+    assert resource.Type == "AWS::SNS::Topic"
diff --git a/tests/test_resolver.py b/tests/test_resolver.py
index f44f87f5..a81e46cd 100644
--- a/tests/test_resolver.py
+++ b/tests/test_resolver.py
@@ -676,3 +676,20 @@ def test_resolve_booleans_different_properties_for_generic_resource():
     assert resource.Properties.PropertyThree is True
     assert resource.Properties.PropertyFour is True
     assert resource.Properties.Policy.Statement[0].Condition.Bool["kms:GrantIsForAWSResource"] is True
+
+
+def test_resolve_template_with_a_valid_resource_without_properties():
+    template = {
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "MySNSTopic": {
+                "Type": "AWS::SNS::Topic",
+            }
+        },
+    }
+
+    model = parse(template).resolve()
+    resource = model.Resources["MySNSTopic"]
+    assert isinstance(resource, GenericResource)
+    assert resource.Properties is None
+    assert resource.Type == "AWS::SNS::Topic"