cartography 0.102.0rc1__py3-none-any.whl → 0.103.0__py3-none-any.whl

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 cartography might be problematic. Click here for more details.

Files changed (297) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +2 -2
  3. cartography/cli.py +376 -249
  4. cartography/client/core/tx.py +39 -18
  5. cartography/config.py +28 -0
  6. cartography/driftdetect/__main__.py +1 -2
  7. cartography/driftdetect/add_shortcut.py +10 -2
  8. cartography/driftdetect/cli.py +71 -75
  9. cartography/driftdetect/detect_deviations.py +7 -3
  10. cartography/driftdetect/get_states.py +20 -8
  11. cartography/driftdetect/model.py +5 -5
  12. cartography/driftdetect/serializers.py +8 -6
  13. cartography/driftdetect/storage.py +2 -2
  14. cartography/graph/cleanupbuilder.py +35 -15
  15. cartography/graph/job.py +46 -17
  16. cartography/graph/querybuilder.py +165 -80
  17. cartography/graph/statement.py +35 -26
  18. cartography/intel/analysis.py +4 -1
  19. cartography/intel/aws/__init__.py +114 -55
  20. cartography/intel/aws/apigateway.py +134 -63
  21. cartography/intel/aws/cloudtrail.py +127 -0
  22. cartography/intel/aws/cloudwatch.py +93 -0
  23. cartography/intel/aws/config.py +56 -20
  24. cartography/intel/aws/dynamodb.py +108 -40
  25. cartography/intel/aws/ec2/__init__.py +2 -2
  26. cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
  27. cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
  28. cartography/intel/aws/ec2/images.py +49 -20
  29. cartography/intel/aws/ec2/instances.py +234 -136
  30. cartography/intel/aws/ec2/internet_gateways.py +40 -11
  31. cartography/intel/aws/ec2/key_pairs.py +44 -20
  32. cartography/intel/aws/ec2/launch_templates.py +101 -59
  33. cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
  34. cartography/intel/aws/ec2/load_balancers.py +82 -42
  35. cartography/intel/aws/ec2/network_acls.py +89 -65
  36. cartography/intel/aws/ec2/network_interfaces.py +146 -87
  37. cartography/intel/aws/ec2/reserved_instances.py +45 -16
  38. cartography/intel/aws/ec2/route_tables.py +327 -0
  39. cartography/intel/aws/ec2/security_groups.py +71 -21
  40. cartography/intel/aws/ec2/snapshots.py +61 -22
  41. cartography/intel/aws/ec2/subnets.py +54 -18
  42. cartography/intel/aws/ec2/tgw.py +100 -34
  43. cartography/intel/aws/ec2/util.py +1 -1
  44. cartography/intel/aws/ec2/volumes.py +69 -41
  45. cartography/intel/aws/ec2/vpc.py +37 -12
  46. cartography/intel/aws/ec2/vpc_peerings.py +83 -24
  47. cartography/intel/aws/ecr.py +88 -32
  48. cartography/intel/aws/ecs.py +83 -47
  49. cartography/intel/aws/efs.py +93 -0
  50. cartography/intel/aws/eks.py +55 -29
  51. cartography/intel/aws/elasticache.py +42 -18
  52. cartography/intel/aws/elasticsearch.py +57 -20
  53. cartography/intel/aws/emr.py +61 -23
  54. cartography/intel/aws/iam.py +401 -145
  55. cartography/intel/aws/iam_instance_profiles.py +22 -22
  56. cartography/intel/aws/identitycenter.py +71 -37
  57. cartography/intel/aws/inspector.py +159 -89
  58. cartography/intel/aws/kms.py +92 -38
  59. cartography/intel/aws/lambda_function.py +103 -34
  60. cartography/intel/aws/organizations.py +30 -10
  61. cartography/intel/aws/permission_relationships.py +133 -51
  62. cartography/intel/aws/rds.py +249 -85
  63. cartography/intel/aws/redshift.py +107 -46
  64. cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
  65. cartography/intel/aws/resources.py +57 -44
  66. cartography/intel/aws/route53.py +108 -61
  67. cartography/intel/aws/s3.py +168 -83
  68. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  69. cartography/intel/aws/secretsmanager.py +24 -12
  70. cartography/intel/aws/securityhub.py +20 -9
  71. cartography/intel/aws/sns.py +166 -0
  72. cartography/intel/aws/sqs.py +60 -28
  73. cartography/intel/aws/ssm.py +70 -30
  74. cartography/intel/aws/util/arns.py +7 -7
  75. cartography/intel/aws/util/common.py +31 -4
  76. cartography/intel/azure/__init__.py +78 -19
  77. cartography/intel/azure/compute.py +101 -27
  78. cartography/intel/azure/cosmosdb.py +496 -170
  79. cartography/intel/azure/sql.py +296 -105
  80. cartography/intel/azure/storage.py +322 -113
  81. cartography/intel/azure/subscription.py +39 -23
  82. cartography/intel/azure/tenant.py +13 -4
  83. cartography/intel/azure/util/credentials.py +95 -55
  84. cartography/intel/bigfix/__init__.py +2 -2
  85. cartography/intel/bigfix/computers.py +93 -65
  86. cartography/intel/cloudflare/__init__.py +74 -0
  87. cartography/intel/cloudflare/accounts.py +57 -0
  88. cartography/intel/cloudflare/dnsrecords.py +64 -0
  89. cartography/intel/cloudflare/members.py +75 -0
  90. cartography/intel/cloudflare/roles.py +65 -0
  91. cartography/intel/cloudflare/zones.py +64 -0
  92. cartography/intel/create_indexes.py +3 -2
  93. cartography/intel/crowdstrike/__init__.py +11 -9
  94. cartography/intel/crowdstrike/endpoints.py +5 -1
  95. cartography/intel/crowdstrike/spotlight.py +8 -3
  96. cartography/intel/cve/__init__.py +46 -13
  97. cartography/intel/cve/feed.py +48 -12
  98. cartography/intel/digitalocean/__init__.py +22 -13
  99. cartography/intel/digitalocean/compute.py +75 -108
  100. cartography/intel/digitalocean/management.py +44 -80
  101. cartography/intel/digitalocean/platform.py +48 -43
  102. cartography/intel/dns.py +36 -10
  103. cartography/intel/duo/__init__.py +21 -16
  104. cartography/intel/duo/api_host.py +14 -9
  105. cartography/intel/duo/endpoints.py +50 -45
  106. cartography/intel/duo/groups.py +18 -14
  107. cartography/intel/duo/phones.py +37 -34
  108. cartography/intel/duo/tokens.py +26 -23
  109. cartography/intel/duo/users.py +54 -50
  110. cartography/intel/duo/web_authn_credentials.py +30 -25
  111. cartography/intel/entra/__init__.py +25 -7
  112. cartography/intel/entra/ou.py +112 -0
  113. cartography/intel/entra/users.py +69 -63
  114. cartography/intel/gcp/__init__.py +185 -49
  115. cartography/intel/gcp/compute.py +418 -231
  116. cartography/intel/gcp/crm.py +96 -43
  117. cartography/intel/gcp/dns.py +60 -19
  118. cartography/intel/gcp/gke.py +72 -38
  119. cartography/intel/gcp/iam.py +61 -41
  120. cartography/intel/gcp/storage.py +84 -55
  121. cartography/intel/github/__init__.py +13 -11
  122. cartography/intel/github/repos.py +270 -137
  123. cartography/intel/github/teams.py +170 -88
  124. cartography/intel/github/users.py +70 -39
  125. cartography/intel/github/util.py +36 -34
  126. cartography/intel/gsuite/__init__.py +47 -26
  127. cartography/intel/gsuite/api.py +73 -30
  128. cartography/intel/jamf/__init__.py +19 -1
  129. cartography/intel/jamf/computers.py +30 -7
  130. cartography/intel/jamf/util.py +7 -2
  131. cartography/intel/kandji/__init__.py +6 -3
  132. cartography/intel/kandji/devices.py +14 -8
  133. cartography/intel/kubernetes/namespaces.py +7 -4
  134. cartography/intel/kubernetes/pods.py +7 -4
  135. cartography/intel/kubernetes/services.py +8 -4
  136. cartography/intel/lastpass/__init__.py +2 -2
  137. cartography/intel/lastpass/users.py +23 -12
  138. cartography/intel/oci/__init__.py +44 -11
  139. cartography/intel/oci/iam.py +134 -38
  140. cartography/intel/oci/organizations.py +13 -6
  141. cartography/intel/oci/utils.py +43 -20
  142. cartography/intel/okta/__init__.py +66 -15
  143. cartography/intel/okta/applications.py +42 -20
  144. cartography/intel/okta/awssaml.py +93 -33
  145. cartography/intel/okta/factors.py +16 -4
  146. cartography/intel/okta/groups.py +56 -29
  147. cartography/intel/okta/organization.py +5 -1
  148. cartography/intel/okta/origins.py +6 -2
  149. cartography/intel/okta/roles.py +15 -5
  150. cartography/intel/okta/users.py +20 -8
  151. cartography/intel/okta/utils.py +6 -4
  152. cartography/intel/openai/__init__.py +86 -0
  153. cartography/intel/openai/adminapikeys.py +90 -0
  154. cartography/intel/openai/apikeys.py +96 -0
  155. cartography/intel/openai/projects.py +94 -0
  156. cartography/intel/openai/serviceaccounts.py +82 -0
  157. cartography/intel/openai/users.py +78 -0
  158. cartography/intel/openai/util.py +29 -0
  159. cartography/intel/pagerduty/__init__.py +8 -7
  160. cartography/intel/pagerduty/escalation_policies.py +18 -6
  161. cartography/intel/pagerduty/schedules.py +12 -4
  162. cartography/intel/pagerduty/services.py +11 -4
  163. cartography/intel/pagerduty/teams.py +8 -3
  164. cartography/intel/pagerduty/users.py +3 -1
  165. cartography/intel/pagerduty/vendors.py +3 -1
  166. cartography/intel/semgrep/__init__.py +24 -6
  167. cartography/intel/semgrep/dependencies.py +50 -28
  168. cartography/intel/semgrep/deployment.py +3 -1
  169. cartography/intel/semgrep/findings.py +42 -18
  170. cartography/intel/snipeit/__init__.py +17 -3
  171. cartography/intel/snipeit/asset.py +12 -6
  172. cartography/intel/snipeit/user.py +8 -5
  173. cartography/intel/snipeit/util.py +9 -4
  174. cartography/intel/tailscale/__init__.py +77 -0
  175. cartography/intel/tailscale/acls.py +146 -0
  176. cartography/intel/tailscale/devices.py +127 -0
  177. cartography/intel/tailscale/postureintegrations.py +81 -0
  178. cartography/intel/tailscale/tailnets.py +76 -0
  179. cartography/intel/tailscale/users.py +80 -0
  180. cartography/intel/tailscale/utils.py +132 -0
  181. cartography/models/aws/apigateway.py +21 -17
  182. cartography/models/aws/apigatewaycertificate.py +28 -22
  183. cartography/models/aws/apigatewayresource.py +28 -20
  184. cartography/models/aws/apigatewaystage.py +33 -25
  185. cartography/models/aws/cloudtrail/__init__.py +0 -0
  186. cartography/models/aws/cloudtrail/trail.py +61 -0
  187. cartography/models/aws/cloudwatch/__init__.py +0 -0
  188. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  189. cartography/models/aws/dynamodb/gsi.py +30 -22
  190. cartography/models/aws/dynamodb/tables.py +25 -17
  191. cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
  192. cartography/models/aws/ec2/images.py +36 -34
  193. cartography/models/aws/ec2/instances.py +51 -45
  194. cartography/models/aws/ec2/keypair.py +21 -16
  195. cartography/models/aws/ec2/keypair_instance.py +28 -21
  196. cartography/models/aws/ec2/launch_configurations.py +30 -26
  197. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  198. cartography/models/aws/ec2/launch_templates.py +21 -17
  199. cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
  200. cartography/models/aws/ec2/load_balancers.py +47 -37
  201. cartography/models/aws/ec2/network_acl_rules.py +38 -30
  202. cartography/models/aws/ec2/network_acls.py +38 -29
  203. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  204. cartography/models/aws/ec2/networkinterfaces.py +53 -37
  205. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  206. cartography/models/aws/ec2/reservations.py +18 -14
  207. cartography/models/aws/ec2/route_table_associations.py +97 -0
  208. cartography/models/aws/ec2/route_tables.py +128 -0
  209. cartography/models/aws/ec2/routes.py +85 -0
  210. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  211. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  212. cartography/models/aws/ec2/subnet_instance.py +24 -19
  213. cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
  214. cartography/models/aws/ec2/volumes.py +47 -40
  215. cartography/models/aws/efs/__init__.py +0 -0
  216. cartography/models/aws/efs/mount_target.py +52 -0
  217. cartography/models/aws/eks/clusters.py +23 -21
  218. cartography/models/aws/emr.py +32 -30
  219. cartography/models/aws/iam/instanceprofile.py +33 -24
  220. cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
  221. cartography/models/aws/identitycenter/awspermissionset.py +37 -29
  222. cartography/models/aws/identitycenter/awsssouser.py +23 -21
  223. cartography/models/aws/inspector/findings.py +77 -65
  224. cartography/models/aws/inspector/packages.py +35 -29
  225. cartography/models/aws/s3/__init__.py +0 -0
  226. cartography/models/aws/s3/account_public_access_block.py +51 -0
  227. cartography/models/aws/sns/__init__.py +0 -0
  228. cartography/models/aws/sns/topic.py +50 -0
  229. cartography/models/aws/ssm/instance_information.py +51 -39
  230. cartography/models/aws/ssm/instance_patch.py +32 -26
  231. cartography/models/bigfix/bigfix_computer.py +42 -38
  232. cartography/models/bigfix/bigfix_root.py +3 -3
  233. cartography/models/cloudflare/__init__.py +0 -0
  234. cartography/models/cloudflare/account.py +25 -0
  235. cartography/models/cloudflare/dnsrecord.py +55 -0
  236. cartography/models/cloudflare/member.py +82 -0
  237. cartography/models/cloudflare/role.py +44 -0
  238. cartography/models/cloudflare/zone.py +59 -0
  239. cartography/models/core/common.py +12 -10
  240. cartography/models/core/nodes.py +5 -2
  241. cartography/models/core/relationships.py +14 -6
  242. cartography/models/crowdstrike/hosts.py +37 -35
  243. cartography/models/cve/cve.py +34 -32
  244. cartography/models/cve/cve_feed.py +6 -6
  245. cartography/models/digitalocean/__init__.py +0 -0
  246. cartography/models/digitalocean/account.py +21 -0
  247. cartography/models/digitalocean/droplet.py +56 -0
  248. cartography/models/digitalocean/project.py +48 -0
  249. cartography/models/duo/api_host.py +3 -3
  250. cartography/models/duo/endpoint.py +43 -41
  251. cartography/models/duo/group.py +14 -14
  252. cartography/models/duo/phone.py +27 -27
  253. cartography/models/duo/token.py +16 -16
  254. cartography/models/duo/user.py +46 -44
  255. cartography/models/duo/web_authn_credential.py +27 -19
  256. cartography/models/entra/ou.py +48 -0
  257. cartography/models/entra/tenant.py +24 -18
  258. cartography/models/entra/user.py +64 -48
  259. cartography/models/gcp/iam.py +23 -23
  260. cartography/models/github/orgs.py +5 -4
  261. cartography/models/github/teams.py +37 -31
  262. cartography/models/github/users.py +34 -23
  263. cartography/models/kandji/device.py +22 -16
  264. cartography/models/kandji/tenant.py +6 -4
  265. cartography/models/lastpass/tenant.py +3 -3
  266. cartography/models/lastpass/user.py +32 -28
  267. cartography/models/openai/__init__.py +0 -0
  268. cartography/models/openai/adminapikey.py +90 -0
  269. cartography/models/openai/apikey.py +84 -0
  270. cartography/models/openai/organization.py +17 -0
  271. cartography/models/openai/project.py +70 -0
  272. cartography/models/openai/serviceaccount.py +50 -0
  273. cartography/models/openai/user.py +49 -0
  274. cartography/models/semgrep/dependencies.py +36 -24
  275. cartography/models/semgrep/deployment.py +5 -5
  276. cartography/models/semgrep/findings.py +58 -42
  277. cartography/models/semgrep/locations.py +27 -21
  278. cartography/models/snipeit/asset.py +30 -21
  279. cartography/models/snipeit/tenant.py +6 -4
  280. cartography/models/snipeit/user.py +19 -12
  281. cartography/models/tailscale/__init__.py +0 -0
  282. cartography/models/tailscale/device.py +95 -0
  283. cartography/models/tailscale/group.py +86 -0
  284. cartography/models/tailscale/postureintegration.py +58 -0
  285. cartography/models/tailscale/tag.py +102 -0
  286. cartography/models/tailscale/tailnet.py +29 -0
  287. cartography/models/tailscale/user.py +52 -0
  288. cartography/stats.py +3 -3
  289. cartography/sync.py +113 -31
  290. cartography/util.py +84 -62
  291. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
  292. cartography-0.103.0.dist-info/RECORD +442 -0
  293. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
  294. cartography-0.102.0rc1.dist-info/RECORD +0 -377
  295. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
  296. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
  297. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
@@ -20,8 +20,8 @@ logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
22
  def _build_node_properties_statement(
23
- node_property_map: Dict[str, PropertyRef],
24
- extra_node_labels: Optional[ExtraNodeLabels] = None,
23
+ node_property_map: Dict[str, PropertyRef],
24
+ extra_node_labels: Optional[ExtraNodeLabels] = None,
25
25
  ) -> str:
26
26
  """
27
27
  Generate a Neo4j clause that sets node properties using the given mapping of attribute names to PropertyRefs.
@@ -46,22 +46,31 @@ def _build_node_properties_statement(
46
46
  :param extra_node_labels: Optional ExtraNodeLabels object to set on the node as string
47
47
  :return: The resulting Neo4j SET clause to set the given attributes on the node
48
48
  """
49
- ingest_fields_template = Template('i.$node_property = $property_ref')
49
+ ingest_fields_template = Template("i.$node_property = $property_ref")
50
50
 
51
- set_clause = ',\n'.join([
52
- ingest_fields_template.safe_substitute(node_property=node_property, property_ref=property_ref)
53
- for node_property, property_ref in node_property_map.items()
54
- if node_property != 'id' # The `MERGE` clause will have already set `id`; let's not set it again.
55
- ])
51
+ set_clause = ",\n".join(
52
+ [
53
+ ingest_fields_template.safe_substitute(
54
+ node_property=node_property,
55
+ property_ref=property_ref,
56
+ )
57
+ for node_property, property_ref in node_property_map.items()
58
+ if node_property
59
+ != "id" # The `MERGE` clause will have already set `id`; let's not set it again.
60
+ ],
61
+ )
56
62
 
57
63
  # Set extra labels on the node if specified
58
64
  if extra_node_labels:
59
- extra_labels = ':'.join([label for label in extra_node_labels.labels])
65
+ extra_labels = ":".join([label for label in extra_node_labels.labels])
60
66
  set_clause += f",\n i:{extra_labels}"
61
67
  return set_clause
62
68
 
63
69
 
64
- def _build_rel_properties_statement(rel_var: str, rel_property_map: Optional[Dict[str, PropertyRef]] = None) -> str:
70
+ def _build_rel_properties_statement(
71
+ rel_var: str,
72
+ rel_property_map: Optional[Dict[str, PropertyRef]] = None,
73
+ ) -> str:
65
74
  """
66
75
  Generate a Neo4j clause that sets relationship properties using the given mapping of attribute names to
67
76
  PropertyRefs.
@@ -83,18 +92,20 @@ def _build_rel_properties_statement(rel_var: str, rel_property_map: Optional[Dic
83
92
  :param rel_property_map: Mapping of relationship attribute names as str to PropertyRef objects
84
93
  :return: The resulting Neo4j SET clause to set the given attributes on the relationship
85
94
  """
86
- set_clause = ''
87
- ingest_fields_template = Template('$rel_var.$rel_property = $property_ref')
95
+ set_clause = ""
96
+ ingest_fields_template = Template("$rel_var.$rel_property = $property_ref")
88
97
 
89
98
  if rel_property_map:
90
- set_clause += ',\n'.join([
91
- ingest_fields_template.safe_substitute(
92
- rel_var=rel_var,
93
- rel_property=rel_property,
94
- property_ref=property_ref,
95
- )
96
- for rel_property, property_ref in rel_property_map.items()
97
- ])
99
+ set_clause += ",\n".join(
100
+ [
101
+ ingest_fields_template.safe_substitute(
102
+ rel_var=rel_var,
103
+ rel_property=rel_property,
104
+ property_ref=property_ref,
105
+ )
106
+ for rel_property, property_ref in rel_property_map.items()
107
+ ],
108
+ )
98
109
  return set_clause
99
110
 
100
111
 
@@ -106,12 +117,15 @@ def _build_match_clause(matcher: TargetNodeMatcher) -> str:
106
117
  """
107
118
  match = Template("$Key: $PropRef")
108
119
  matcher_asdict = asdict(matcher)
109
- return ', '.join(match.safe_substitute(Key=key, PropRef=prop_ref) for key, prop_ref in matcher_asdict.items())
120
+ return ", ".join(
121
+ match.safe_substitute(Key=key, PropRef=prop_ref)
122
+ for key, prop_ref in matcher_asdict.items()
123
+ )
110
124
 
111
125
 
112
126
  def _build_where_clause_for_rel_match(
113
- node_var: str,
114
- matcher: TargetNodeMatcher,
127
+ node_var: str,
128
+ matcher: TargetNodeMatcher,
115
129
  ) -> str:
116
130
  """
117
131
  Same as _build_match_clause, but puts the matching logic in a WHERE clause.
@@ -121,7 +135,9 @@ def _build_where_clause_for_rel_match(
121
135
  """
122
136
  match = Template("$node_var.$key = $prop_ref")
123
137
  case_insensitive_match = Template("toLower($node_var.$key) = toLower($prop_ref)")
124
- fuzzy_and_ignorecase_match = Template("toLower($node_var.$key) CONTAINS toLower($prop_ref)")
138
+ fuzzy_and_ignorecase_match = Template(
139
+ "toLower($node_var.$key) CONTAINS toLower($prop_ref)"
140
+ )
125
141
  # This assumes that item.$prop_ref points to a list available on the data object
126
142
  one_to_many_match = Template("$node_var.$key IN $prop_ref")
127
143
 
@@ -130,20 +146,34 @@ def _build_where_clause_for_rel_match(
130
146
  result = []
131
147
  for key, prop_ref in matcher_asdict.items():
132
148
  if prop_ref.ignore_case:
133
- prop_line = case_insensitive_match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
149
+ prop_line = case_insensitive_match.safe_substitute(
150
+ node_var=node_var,
151
+ key=key,
152
+ prop_ref=prop_ref,
153
+ )
134
154
  elif prop_ref.fuzzy_and_ignore_case:
135
- prop_line = fuzzy_and_ignorecase_match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
155
+ prop_line = fuzzy_and_ignorecase_match.safe_substitute(
156
+ node_var=node_var, key=key, prop_ref=prop_ref
157
+ )
136
158
  elif prop_ref.one_to_many:
137
159
  # Allow a single node to be attached to multiple others at once using a list of IDs provided in kwargs
138
- prop_line = one_to_many_match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
160
+ prop_line = one_to_many_match.safe_substitute(
161
+ node_var=node_var, key=key, prop_ref=prop_ref
162
+ )
139
163
  else:
140
164
  # Exact match (default; most efficient)
141
- prop_line = match.safe_substitute(node_var=node_var, key=key, prop_ref=prop_ref)
165
+ prop_line = match.safe_substitute(
166
+ node_var=node_var,
167
+ key=key,
168
+ prop_ref=prop_ref,
169
+ )
142
170
  result.append(prop_line)
143
- return ' AND\n'.join(result)
171
+ return " AND\n".join(result)
144
172
 
145
173
 
146
- def _asdict_with_validate_relprops(link: CartographyRelSchema) -> Dict[str, PropertyRef]:
174
+ def _asdict_with_validate_relprops(
175
+ link: CartographyRelSchema,
176
+ ) -> Dict[str, PropertyRef]:
147
177
  """
148
178
  Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
149
179
  isn't always caught by IDEs.
@@ -151,18 +181,24 @@ def _asdict_with_validate_relprops(link: CartographyRelSchema) -> Dict[str, Prop
151
181
  try:
152
182
  rel_props_as_dict: Dict[str, PropertyRef] = asdict(link.properties)
153
183
  except TypeError as e:
154
- if e.args and e.args[0] and e.args == 'asdict() should be called on dataclass instances':
184
+ if (
185
+ e.args
186
+ and e.args[0]
187
+ and e.args == "asdict() should be called on dataclass instances"
188
+ ):
155
189
  logger.error(
156
190
  f'TypeError thrown when trying to draw relation "{link.rel_label}" to a "{link.target_node_label}" '
157
- f'node. Please make sure that you did not forget to write `()` when specifying `properties` in the'
158
- f'dataclass. '
159
- f'For example, do `properties: RelProp = RelProp()`; NOT `properties: RelProp = RelProp`.',
191
+ f"node. Please make sure that you did not forget to write `()` when specifying `properties` in the"
192
+ f"dataclass. "
193
+ f"For example, do `properties: RelProp = RelProp()`; NOT `properties: RelProp = RelProp`.",
160
194
  )
161
195
  raise
162
196
  return rel_props_as_dict
163
197
 
164
198
 
165
- def _build_attach_sub_resource_statement(sub_resource_link: Optional[CartographyRelSchema] = None) -> str:
199
+ def _build_attach_sub_resource_statement(
200
+ sub_resource_link: Optional[CartographyRelSchema] = None,
201
+ ) -> str:
166
202
  """
167
203
  Generates a Neo4j statement to attach a sub resource to a node. A 'sub resource' is a term we made up to describe
168
204
  billing units of a given resource. For example,
@@ -176,7 +212,7 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
176
212
  keys, and directionality. If sub_resource_link is None, return an empty string.
177
213
  """
178
214
  if not sub_resource_link:
179
- return ''
215
+ return ""
180
216
 
181
217
  sub_resource_attach_template = Template(
182
218
  """
@@ -195,22 +231,29 @@ def _build_attach_sub_resource_statement(sub_resource_link: Optional[Cartography
195
231
  else:
196
232
  rel_merge_template = Template("""MERGE (i)-[r:$SubResourceRelLabel]->(j)""")
197
233
 
198
- rel_merge_clause = rel_merge_template.safe_substitute(SubResourceRelLabel=sub_resource_link.rel_label)
234
+ rel_merge_clause = rel_merge_template.safe_substitute(
235
+ SubResourceRelLabel=sub_resource_link.rel_label,
236
+ )
199
237
 
200
- rel_props_as_dict: Dict[str, PropertyRef] = _asdict_with_validate_relprops(sub_resource_link)
238
+ rel_props_as_dict: Dict[str, PropertyRef] = _asdict_with_validate_relprops(
239
+ sub_resource_link,
240
+ )
201
241
 
202
242
  attach_sub_resource_statement = sub_resource_attach_template.safe_substitute(
203
243
  SubResourceLabel=sub_resource_link.target_node_label,
204
244
  MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
205
245
  RelMergeClause=rel_merge_clause,
206
246
  SubResourceRelLabel=sub_resource_link.rel_label,
207
- set_rel_properties_statement=_build_rel_properties_statement('r', rel_props_as_dict),
247
+ set_rel_properties_statement=_build_rel_properties_statement(
248
+ "r",
249
+ rel_props_as_dict,
250
+ ),
208
251
  )
209
252
  return attach_sub_resource_statement
210
253
 
211
254
 
212
255
  def _build_attach_additional_links_statement(
213
- additional_relationships: Optional[OtherRelationships] = None,
256
+ additional_relationships: Optional[OtherRelationships] = None,
214
257
  ) -> str:
215
258
  """
216
259
  Generates a Neo4j statement to attach one or more CartographyRelSchemas to node(s) previously mentioned in the
@@ -222,7 +265,7 @@ def _build_attach_additional_links_statement(
222
265
  labels, attribute keys, and directionality. If additional_relationships is None, return an empty string.
223
266
  """
224
267
  if not additional_relationships:
225
- return ''
268
+ return ""
226
269
 
227
270
  additional_links_template = Template(
228
271
  """
@@ -243,9 +286,13 @@ def _build_attach_additional_links_statement(
243
286
  rel_var = f"r{num}"
244
287
 
245
288
  if link.direction == LinkDirection.INWARD:
246
- rel_merge_template = Template("""MERGE (i)<-[$rel_var:$AddlRelLabel]-($node_var)""")
289
+ rel_merge_template = Template(
290
+ """MERGE (i)<-[$rel_var:$AddlRelLabel]-($node_var)""",
291
+ )
247
292
  else:
248
- rel_merge_template = Template("""MERGE (i)-[$rel_var:$AddlRelLabel]->($node_var)""")
293
+ rel_merge_template = Template(
294
+ """MERGE (i)-[$rel_var:$AddlRelLabel]->($node_var)""",
295
+ )
249
296
 
250
297
  rel_merge = rel_merge_template.safe_substitute(
251
298
  rel_var=rel_var,
@@ -257,20 +304,26 @@ def _build_attach_additional_links_statement(
257
304
 
258
305
  additional_ref = additional_links_template.safe_substitute(
259
306
  AddlLabel=link.target_node_label,
260
- WhereClause=_build_where_clause_for_rel_match(node_var, link.target_node_matcher),
307
+ WhereClause=_build_where_clause_for_rel_match(
308
+ node_var,
309
+ link.target_node_matcher,
310
+ ),
261
311
  node_var=node_var,
262
312
  rel_var=rel_var,
263
313
  RelMerge=rel_merge,
264
- set_rel_properties_statement=_build_rel_properties_statement(rel_var, rel_props_as_dict),
314
+ set_rel_properties_statement=_build_rel_properties_statement(
315
+ rel_var,
316
+ rel_props_as_dict,
317
+ ),
265
318
  )
266
319
  links.append(additional_ref)
267
320
 
268
- return 'UNION'.join(links)
321
+ return "UNION".join(links)
269
322
 
270
323
 
271
324
  def _build_attach_relationships_statement(
272
- sub_resource_relationship: Optional[CartographyRelSchema],
273
- other_relationships: Optional[OtherRelationships],
325
+ sub_resource_relationship: Optional[CartographyRelSchema],
326
+ other_relationships: Optional[OtherRelationships],
274
327
  ) -> str:
275
328
  """
276
329
  Use Neo4j subqueries to attach sub resource and/or other relationships.
@@ -283,14 +336,22 @@ def _build_attach_relationships_statement(
283
336
  if not sub_resource_relationship and not other_relationships:
284
337
  return ""
285
338
 
286
- attach_sub_resource_statement = _build_attach_sub_resource_statement(sub_resource_relationship)
287
- attach_additional_links_statement = _build_attach_additional_links_statement(other_relationships)
339
+ attach_sub_resource_statement = _build_attach_sub_resource_statement(
340
+ sub_resource_relationship,
341
+ )
342
+ attach_additional_links_statement = _build_attach_additional_links_statement(
343
+ other_relationships,
344
+ )
288
345
 
289
346
  statements = []
290
- statements += [attach_sub_resource_statement] if attach_sub_resource_statement else []
291
- statements += [attach_additional_links_statement] if attach_additional_links_statement else []
347
+ statements += (
348
+ [attach_sub_resource_statement] if attach_sub_resource_statement else []
349
+ )
350
+ statements += (
351
+ [attach_additional_links_statement] if attach_additional_links_statement else []
352
+ )
292
353
 
293
- attach_relationships_statement = 'UNION'.join(stmt for stmt in statements)
354
+ attach_relationships_statement = "UNION".join(stmt for stmt in statements)
294
355
 
295
356
  query_template = Template(
296
357
  """
@@ -300,12 +361,14 @@ def _build_attach_relationships_statement(
300
361
  }
301
362
  """,
302
363
  )
303
- return query_template.safe_substitute(attach_relationships_statement=attach_relationships_statement)
364
+ return query_template.safe_substitute(
365
+ attach_relationships_statement=attach_relationships_statement,
366
+ )
304
367
 
305
368
 
306
369
  def rel_present_on_node_schema(
307
- node_schema: CartographyNodeSchema,
308
- rel_schema: CartographyRelSchema,
370
+ node_schema: CartographyNodeSchema,
371
+ rel_schema: CartographyRelSchema,
309
372
  ) -> bool:
310
373
  """
311
374
  Answers the question: is the given rel_schema is present on the given node_schema?
@@ -317,8 +380,8 @@ def rel_present_on_node_schema(
317
380
 
318
381
 
319
382
  def filter_selected_relationships(
320
- node_schema: CartographyNodeSchema,
321
- selected_relationships: Set[CartographyRelSchema],
383
+ node_schema: CartographyNodeSchema,
384
+ selected_relationships: Set[CartographyRelSchema],
322
385
  ) -> Tuple[Optional[CartographyRelSchema], Optional[OtherRelationships]]:
323
386
  """
324
387
  Ensures that selected relationships specified to build_ingestion_query() are actually present on
@@ -353,14 +416,16 @@ def filter_selected_relationships(
353
416
  sub_resource_rel = None
354
417
 
355
418
  # By this point, everything in selected_relationships is validated to be present in node_schema
356
- filtered_other_rels = OtherRelationships([rel for rel in selected_relationships if rel != sub_resource_rel])
419
+ filtered_other_rels = OtherRelationships(
420
+ [rel for rel in selected_relationships if rel != sub_resource_rel],
421
+ )
357
422
 
358
423
  return sub_resource_rel, filtered_other_rels
359
424
 
360
425
 
361
426
  def build_ingestion_query(
362
- node_schema: CartographyNodeSchema,
363
- selected_relationships: Optional[Set[CartographyRelSchema]] = None,
427
+ node_schema: CartographyNodeSchema,
428
+ selected_relationships: Optional[Set[CartographyRelSchema]] = None,
364
429
  ) -> str:
365
430
  """
366
431
  Generates a Neo4j query from the given CartographyNodeSchema to ingest the specified nodes and relationships so that
@@ -396,10 +461,15 @@ def build_ingestion_query(
396
461
  node_props_as_dict: Dict[str, PropertyRef] = asdict(node_props)
397
462
 
398
463
  # Handle selected relationships
399
- sub_resource_rel: Optional[CartographyRelSchema] = node_schema.sub_resource_relationship
464
+ sub_resource_rel: Optional[CartographyRelSchema] = (
465
+ node_schema.sub_resource_relationship
466
+ )
400
467
  other_rels: Optional[OtherRelationships] = node_schema.other_relationships
401
468
  if selected_relationships or selected_relationships == set():
402
- sub_resource_rel, other_rels = filter_selected_relationships(node_schema, selected_relationships)
469
+ sub_resource_rel, other_rels = filter_selected_relationships(
470
+ node_schema,
471
+ selected_relationships,
472
+ )
403
473
 
404
474
  ingest_query = query_template.safe_substitute(
405
475
  node_label=node_schema.label,
@@ -408,7 +478,10 @@ def build_ingestion_query(
408
478
  node_props_as_dict,
409
479
  node_schema.extra_node_labels,
410
480
  ),
411
- attach_relationships_statement=_build_attach_relationships_statement(sub_resource_rel, other_rels),
481
+ attach_relationships_statement=_build_attach_relationships_statement(
482
+ sub_resource_rel,
483
+ other_rels,
484
+ ),
412
485
  )
413
486
  return ingest_query
414
487
 
@@ -420,26 +493,31 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
420
493
  :param node_schema: The Cartography node_schema object
421
494
  :return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
422
495
  """
423
- index_template = Template('CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);')
496
+ index_template = Template(
497
+ "CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute);",
498
+ )
424
499
 
425
500
  # First ensure an index exists for the node_schema and all extra labels on the `id` and `lastupdated` fields
426
501
  result = [
427
502
  index_template.safe_substitute(
428
503
  TargetNodeLabel=node_schema.label,
429
- TargetAttribute='id',
504
+ TargetAttribute="id",
430
505
  ),
431
506
  index_template.safe_substitute(
432
507
  TargetNodeLabel=node_schema.label,
433
- TargetAttribute='lastupdated',
508
+ TargetAttribute="lastupdated",
434
509
  ),
435
510
  ]
436
511
  if node_schema.extra_node_labels:
437
- result.extend([
438
- index_template.safe_substitute(
439
- TargetNodeLabel=label,
440
- TargetAttribute='id', # Precondition: 'id' is defined on all cartography node_schema objects.
441
- ) for label in node_schema.extra_node_labels.labels
442
- ])
512
+ result.extend(
513
+ [
514
+ index_template.safe_substitute(
515
+ TargetNodeLabel=label,
516
+ TargetAttribute="id", # Precondition: 'id' is defined on all cartography node_schema objects.
517
+ )
518
+ for label in node_schema.extra_node_labels.labels
519
+ ],
520
+ )
443
521
 
444
522
  # Next, for all relationships possible out of this node, ensure that indexes exist for all target nodes' properties
445
523
  # as specified in their TargetNodeMatchers.
@@ -451,15 +529,22 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
451
529
  for rs in rel_schemas:
452
530
  for target_key in asdict(rs.target_node_matcher).keys():
453
531
  result.append(
454
- index_template.safe_substitute(TargetNodeLabel=rs.target_node_label, TargetAttribute=target_key),
532
+ index_template.safe_substitute(
533
+ TargetNodeLabel=rs.target_node_label,
534
+ TargetAttribute=target_key,
535
+ ),
455
536
  )
456
537
 
457
538
  # Now, include extra indexes defined by the module author on the node schema's property refs.
458
539
  node_props_as_dict: Dict[str, PropertyRef] = asdict(node_schema.properties)
459
- result.extend([
460
- index_template.safe_substitute(
461
- TargetNodeLabel=node_schema.label,
462
- TargetAttribute=prop_name,
463
- ) for prop_name, prop_ref in node_props_as_dict.items() if prop_ref.extra_index
464
- ])
540
+ result.extend(
541
+ [
542
+ index_template.safe_substitute(
543
+ TargetNodeLabel=node_schema.label,
544
+ TargetAttribute=prop_name,
545
+ )
546
+ for prop_name, prop_ref in node_props_as_dict.items()
547
+ if prop_ref.extra_index
548
+ ],
549
+ )
465
550
  return result
@@ -11,7 +11,6 @@ import neo4j
11
11
 
12
12
  from cartography.stats import get_stats_client
13
13
 
14
-
15
14
  logger = logging.getLogger(__name__)
16
15
  stat_handler = get_stats_client(__name__)
17
16
 
@@ -41,13 +40,13 @@ class GraphStatement:
41
40
  """
42
41
 
43
42
  def __init__(
44
- self,
45
- query: str,
46
- parameters: Optional[Dict[Any, Any]] = None,
47
- iterative: bool = False,
48
- iterationsize: int = 0,
49
- parent_job_name: Optional[str] = None,
50
- parent_job_sequence_num: Optional[int] = None,
43
+ self,
44
+ query: str,
45
+ parameters: Optional[Dict[Any, Any]] = None,
46
+ iterative: bool = False,
47
+ iterationsize: int = 0,
48
+ parent_job_name: Optional[str] = None,
49
+ parent_job_sequence_num: Optional[int] = None,
51
50
  ):
52
51
  self.query = query
53
52
  self.parameters = parameters or {}
@@ -56,7 +55,9 @@ class GraphStatement:
56
55
  self.parameters["LIMIT_SIZE"] = self.iterationsize
57
56
 
58
57
  self.parent_job_name = parent_job_name if parent_job_name else None
59
- self.parent_job_sequence_num = parent_job_sequence_num if parent_job_sequence_num else None
58
+ self.parent_job_sequence_num = (
59
+ parent_job_sequence_num if parent_job_sequence_num else None
60
+ )
60
61
 
61
62
  def merge_parameters(self, parameters: Dict) -> None:
62
63
  """
@@ -75,7 +76,9 @@ class GraphStatement:
75
76
  else:
76
77
  session.write_transaction(self._run_noniterative)
77
78
 
78
- logger.info(f"Completed {self.parent_job_name} statement #{self.parent_job_sequence_num}")
79
+ logger.info(
80
+ f"Completed {self.parent_job_name} statement #{self.parent_job_sequence_num}"
81
+ )
79
82
 
80
83
  def as_dict(self) -> Dict[str, Any]:
81
84
  """
@@ -99,17 +102,21 @@ class GraphStatement:
99
102
  summary: neo4j.ResultSummary = result.consume()
100
103
 
101
104
  # Handle stats
102
- stat_handler.incr('constraints_added', summary.counters.constraints_added)
103
- stat_handler.incr('constraints_removed', summary.counters.constraints_removed)
104
- stat_handler.incr('indexes_added', summary.counters.indexes_added)
105
- stat_handler.incr('indexes_removed', summary.counters.indexes_removed)
106
- stat_handler.incr('labels_added', summary.counters.labels_added)
107
- stat_handler.incr('labels_removed', summary.counters.labels_removed)
108
- stat_handler.incr('nodes_created', summary.counters.nodes_created)
109
- stat_handler.incr('nodes_deleted', summary.counters.nodes_deleted)
110
- stat_handler.incr('properties_set', summary.counters.properties_set)
111
- stat_handler.incr('relationships_created', summary.counters.relationships_created)
112
- stat_handler.incr('relationships_deleted', summary.counters.relationships_deleted)
105
+ stat_handler.incr("constraints_added", summary.counters.constraints_added)
106
+ stat_handler.incr("constraints_removed", summary.counters.constraints_removed)
107
+ stat_handler.incr("indexes_added", summary.counters.indexes_added)
108
+ stat_handler.incr("indexes_removed", summary.counters.indexes_removed)
109
+ stat_handler.incr("labels_added", summary.counters.labels_added)
110
+ stat_handler.incr("labels_removed", summary.counters.labels_removed)
111
+ stat_handler.incr("nodes_created", summary.counters.nodes_created)
112
+ stat_handler.incr("nodes_deleted", summary.counters.nodes_deleted)
113
+ stat_handler.incr("properties_set", summary.counters.properties_set)
114
+ stat_handler.incr(
115
+ "relationships_created", summary.counters.relationships_created
116
+ )
117
+ stat_handler.incr(
118
+ "relationships_deleted", summary.counters.relationships_deleted
119
+ )
113
120
 
114
121
  return summary
115
122
 
@@ -122,17 +129,19 @@ class GraphStatement:
122
129
  self.parameters["LIMIT_SIZE"] = self.iterationsize
123
130
 
124
131
  while True:
125
- summary: neo4j.ResultSummary = session.write_transaction(self._run_noniterative)
132
+ summary: neo4j.ResultSummary = session.write_transaction(
133
+ self._run_noniterative
134
+ )
126
135
 
127
136
  if not summary.counters.contains_updates:
128
137
  break
129
138
 
130
139
  @classmethod
131
140
  def create_from_json(
132
- cls,
133
- json_obj: Dict[str, Any],
134
- short_job_name: Optional[str] = None,
135
- job_sequence_num: Optional[int] = None,
141
+ cls,
142
+ json_obj: Dict[str, Any],
143
+ short_job_name: Optional[str] = None,
144
+ job_sequence_num: Optional[int] = None,
136
145
  ):
137
146
  """
138
147
  Create a statement from a JSON blob.
@@ -39,4 +39,7 @@ def run(neo4j_session: neo4j.Session, config: Config) -> None:
39
39
  except (KeyboardInterrupt, SystemExit):
40
40
  raise
41
41
  except Exception:
42
- logger.exception("An exception occurred while executing discovered analysis job: %s", path)
42
+ logger.exception(
43
+ "An exception occurred while executing discovered analysis job: %s",
44
+ path,
45
+ )