cartography 0.104.0rc2__py3-none-any.whl → 0.123.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.
Files changed (642) hide show
  1. cartography/_version.py +16 -3
  2. cartography/cli.py +466 -5
  3. cartography/client/aws/__init__.py +19 -0
  4. cartography/client/aws/ecr.py +51 -0
  5. cartography/client/core/tx.py +357 -8
  6. cartography/config.py +153 -0
  7. cartography/data/azure_permission_relationships.yaml +20 -0
  8. cartography/data/gcp_permission_relationships.yaml +21 -0
  9. cartography/data/indexes.cypher +0 -186
  10. cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
  11. cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
  12. cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
  13. cartography/data/jobs/cleanup/github_repos_cleanup.json +2 -0
  14. cartography/driftdetect/cli.py +3 -2
  15. cartography/graph/cleanupbuilder.py +198 -41
  16. cartography/graph/job.py +54 -6
  17. cartography/graph/querybuilder.py +528 -27
  18. cartography/graph/statement.py +5 -1
  19. cartography/intel/airbyte/__init__.py +105 -0
  20. cartography/intel/airbyte/connections.py +120 -0
  21. cartography/intel/airbyte/destinations.py +81 -0
  22. cartography/intel/airbyte/organizations.py +59 -0
  23. cartography/intel/airbyte/sources.py +78 -0
  24. cartography/intel/airbyte/tags.py +64 -0
  25. cartography/intel/airbyte/users.py +106 -0
  26. cartography/intel/airbyte/util.py +122 -0
  27. cartography/intel/airbyte/workspaces.py +63 -0
  28. cartography/intel/aws/__init__.py +24 -9
  29. cartography/intel/aws/acm.py +124 -0
  30. cartography/intel/aws/apigateway.py +253 -22
  31. cartography/intel/aws/apigatewayv2.py +116 -0
  32. cartography/intel/aws/cloudtrail.py +17 -39
  33. cartography/intel/aws/cloudtrail_management_events.py +962 -0
  34. cartography/intel/aws/cloudwatch.py +150 -4
  35. cartography/intel/aws/codebuild.py +132 -0
  36. cartography/intel/aws/cognito.py +201 -0
  37. cartography/intel/aws/config.py +7 -3
  38. cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
  39. cartography/intel/aws/ec2/instances.py +25 -1
  40. cartography/intel/aws/ec2/internet_gateways.py +4 -2
  41. cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
  42. cartography/intel/aws/ec2/network_interfaces.py +5 -1
  43. cartography/intel/aws/ec2/reserved_instances.py +3 -1
  44. cartography/intel/aws/ec2/security_groups.py +140 -122
  45. cartography/intel/aws/ec2/snapshots.py +47 -84
  46. cartography/intel/aws/ec2/subnets.py +37 -63
  47. cartography/intel/aws/ec2/tgw.py +11 -5
  48. cartography/intel/aws/ec2/volumes.py +1 -1
  49. cartography/intel/aws/ec2/vpc.py +140 -124
  50. cartography/intel/aws/ec2/vpc_peerings.py +262 -125
  51. cartography/intel/aws/ecr.py +269 -98
  52. cartography/intel/aws/ecr_image_layers.py +923 -0
  53. cartography/intel/aws/ecs.py +251 -380
  54. cartography/intel/aws/efs.py +179 -11
  55. cartography/intel/aws/elasticache.py +102 -79
  56. cartography/intel/aws/elasticsearch.py +13 -4
  57. cartography/intel/aws/eventbridge.py +164 -0
  58. cartography/intel/aws/glue.py +181 -0
  59. cartography/intel/aws/guardduty.py +443 -0
  60. cartography/intel/aws/iam.py +750 -493
  61. cartography/intel/aws/identitycenter.py +605 -83
  62. cartography/intel/aws/inspector.py +221 -105
  63. cartography/intel/aws/kms.py +173 -201
  64. cartography/intel/aws/lambda_function.py +272 -189
  65. cartography/intel/aws/organizations.py +10 -9
  66. cartography/intel/aws/permission_relationships.py +10 -20
  67. cartography/intel/aws/rds.py +337 -446
  68. cartography/intel/aws/redshift.py +9 -4
  69. cartography/intel/aws/resourcegroupstaggingapi.py +78 -19
  70. cartography/intel/aws/resources.py +18 -0
  71. cartography/intel/aws/route53.py +386 -332
  72. cartography/intel/aws/s3.py +322 -14
  73. cartography/intel/aws/secretsmanager.py +81 -49
  74. cartography/intel/aws/securityhub.py +3 -1
  75. cartography/intel/aws/sns.py +62 -2
  76. cartography/intel/aws/sqs.py +36 -90
  77. cartography/intel/aws/ssm.py +3 -5
  78. cartography/intel/azure/__init__.py +202 -48
  79. cartography/intel/azure/aks.py +175 -0
  80. cartography/intel/azure/app_service.py +105 -0
  81. cartography/intel/azure/compute.py +59 -112
  82. cartography/intel/azure/container_instances.py +95 -0
  83. cartography/intel/azure/cosmosdb.py +222 -361
  84. cartography/intel/azure/data_factory.py +85 -0
  85. cartography/intel/azure/data_factory_dataset.py +128 -0
  86. cartography/intel/azure/data_factory_linked_service.py +119 -0
  87. cartography/intel/azure/data_factory_pipeline.py +142 -0
  88. cartography/intel/azure/data_lake.py +124 -0
  89. cartography/intel/azure/event_grid.py +94 -0
  90. cartography/intel/azure/functions.py +124 -0
  91. cartography/intel/azure/load_balancers.py +263 -0
  92. cartography/intel/azure/logic_apps.py +101 -0
  93. cartography/intel/azure/monitor.py +105 -0
  94. cartography/intel/azure/network.py +467 -0
  95. cartography/intel/azure/permission_relationships.py +466 -0
  96. cartography/intel/azure/rbac.py +309 -0
  97. cartography/intel/azure/resource_groups.py +82 -0
  98. cartography/intel/azure/security_center.py +106 -0
  99. cartography/intel/azure/sql.py +145 -292
  100. cartography/intel/azure/storage.py +185 -262
  101. cartography/intel/azure/subscription.py +21 -43
  102. cartography/intel/azure/tenant.py +39 -30
  103. cartography/intel/azure/util/common.py +13 -0
  104. cartography/intel/azure/util/credentials.py +49 -174
  105. cartography/intel/azure/util/tag.py +41 -0
  106. cartography/intel/create_indexes.py +2 -1
  107. cartography/intel/crowdstrike/spotlight.py +5 -2
  108. cartography/intel/dns.py +5 -2
  109. cartography/intel/entra/__init__.py +100 -1
  110. cartography/intel/entra/app_role_assignments.py +284 -0
  111. cartography/intel/entra/applications.py +182 -0
  112. cartography/intel/entra/federation/__init__.py +0 -0
  113. cartography/intel/entra/federation/aws_identity_center.py +77 -0
  114. cartography/intel/entra/groups.py +198 -0
  115. cartography/intel/entra/ou.py +48 -24
  116. cartography/intel/entra/service_principals.py +217 -0
  117. cartography/intel/entra/users.py +105 -57
  118. cartography/intel/gcp/__init__.py +334 -396
  119. cartography/intel/gcp/bigtable_app_profile.py +101 -0
  120. cartography/intel/gcp/bigtable_backup.py +91 -0
  121. cartography/intel/gcp/bigtable_cluster.py +93 -0
  122. cartography/intel/gcp/bigtable_instance.py +86 -0
  123. cartography/intel/gcp/bigtable_table.py +87 -0
  124. cartography/intel/gcp/cai.py +292 -0
  125. cartography/intel/gcp/clients.py +112 -0
  126. cartography/intel/gcp/compute.py +128 -119
  127. cartography/intel/gcp/crm/__init__.py +0 -0
  128. cartography/intel/gcp/crm/folders.py +114 -0
  129. cartography/intel/gcp/crm/orgs.py +70 -0
  130. cartography/intel/gcp/crm/projects.py +120 -0
  131. cartography/intel/gcp/dns.py +83 -169
  132. cartography/intel/gcp/gke.py +72 -113
  133. cartography/intel/gcp/iam.py +111 -91
  134. cartography/intel/gcp/permission_relationships.py +394 -0
  135. cartography/intel/gcp/policy_bindings.py +225 -0
  136. cartography/intel/gcp/storage.py +75 -159
  137. cartography/intel/github/__init__.py +62 -25
  138. cartography/intel/github/commits.py +423 -0
  139. cartography/intel/github/repos.py +463 -85
  140. cartography/intel/github/teams.py +3 -3
  141. cartography/intel/github/users.py +5 -0
  142. cartography/intel/github/util.py +12 -0
  143. cartography/intel/googleworkspace/__init__.py +193 -0
  144. cartography/intel/googleworkspace/devices.py +254 -0
  145. cartography/intel/googleworkspace/groups.py +568 -0
  146. cartography/intel/googleworkspace/oauth_apps.py +259 -0
  147. cartography/intel/googleworkspace/tenant.py +85 -0
  148. cartography/intel/googleworkspace/users.py +138 -0
  149. cartography/intel/gsuite/__init__.py +17 -9
  150. cartography/intel/gsuite/groups.py +291 -0
  151. cartography/intel/gsuite/users.py +142 -0
  152. cartography/intel/jamf/computers.py +7 -1
  153. cartography/intel/keycloak/__init__.py +153 -0
  154. cartography/intel/keycloak/authenticationexecutions.py +322 -0
  155. cartography/intel/keycloak/authenticationflows.py +77 -0
  156. cartography/intel/keycloak/clients.py +187 -0
  157. cartography/intel/keycloak/groups.py +126 -0
  158. cartography/intel/keycloak/identityproviders.py +94 -0
  159. cartography/intel/keycloak/organizations.py +163 -0
  160. cartography/intel/keycloak/realms.py +61 -0
  161. cartography/intel/keycloak/roles.py +202 -0
  162. cartography/intel/keycloak/scopes.py +73 -0
  163. cartography/intel/keycloak/users.py +70 -0
  164. cartography/intel/keycloak/util.py +47 -0
  165. cartography/intel/kubernetes/__init__.py +60 -14
  166. cartography/intel/kubernetes/clusters.py +86 -0
  167. cartography/intel/kubernetes/eks.py +402 -0
  168. cartography/intel/kubernetes/namespaces.py +59 -57
  169. cartography/intel/kubernetes/pods.py +168 -75
  170. cartography/intel/kubernetes/rbac.py +597 -0
  171. cartography/intel/kubernetes/secrets.py +95 -45
  172. cartography/intel/kubernetes/services.py +131 -67
  173. cartography/intel/kubernetes/util.py +142 -14
  174. cartography/intel/oci/iam.py +23 -9
  175. cartography/intel/oci/organizations.py +3 -1
  176. cartography/intel/oci/utils.py +28 -5
  177. cartography/intel/okta/applications.py +15 -5
  178. cartography/intel/okta/awssaml.py +14 -10
  179. cartography/intel/okta/factors.py +3 -1
  180. cartography/intel/okta/groups.py +5 -2
  181. cartography/intel/okta/organization.py +3 -1
  182. cartography/intel/okta/origins.py +3 -1
  183. cartography/intel/okta/roles.py +5 -2
  184. cartography/intel/okta/users.py +10 -2
  185. cartography/intel/ontology/__init__.py +44 -0
  186. cartography/intel/ontology/devices.py +54 -0
  187. cartography/intel/ontology/users.py +54 -0
  188. cartography/intel/ontology/utils.py +176 -0
  189. cartography/intel/pagerduty/escalation_policies.py +13 -6
  190. cartography/intel/pagerduty/schedules.py +9 -4
  191. cartography/intel/pagerduty/services.py +7 -3
  192. cartography/intel/pagerduty/teams.py +5 -2
  193. cartography/intel/pagerduty/users.py +3 -1
  194. cartography/intel/pagerduty/vendors.py +3 -1
  195. cartography/intel/scaleway/__init__.py +127 -0
  196. cartography/intel/scaleway/iam/__init__.py +0 -0
  197. cartography/intel/scaleway/iam/apikeys.py +71 -0
  198. cartography/intel/scaleway/iam/applications.py +71 -0
  199. cartography/intel/scaleway/iam/groups.py +71 -0
  200. cartography/intel/scaleway/iam/users.py +71 -0
  201. cartography/intel/scaleway/instances/__init__.py +0 -0
  202. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  203. cartography/intel/scaleway/instances/instances.py +92 -0
  204. cartography/intel/scaleway/projects.py +79 -0
  205. cartography/intel/scaleway/storage/__init__.py +0 -0
  206. cartography/intel/scaleway/storage/snapshots.py +86 -0
  207. cartography/intel/scaleway/storage/volumes.py +84 -0
  208. cartography/intel/scaleway/utils.py +37 -0
  209. cartography/intel/sentinelone/__init__.py +75 -0
  210. cartography/intel/sentinelone/account.py +140 -0
  211. cartography/intel/sentinelone/agent.py +139 -0
  212. cartography/intel/sentinelone/api.py +124 -0
  213. cartography/intel/sentinelone/application.py +248 -0
  214. cartography/intel/sentinelone/cve.py +119 -0
  215. cartography/intel/sentinelone/utils.py +28 -0
  216. cartography/intel/slack/__init__.py +78 -0
  217. cartography/intel/slack/channels.py +80 -0
  218. cartography/intel/slack/groups.py +90 -0
  219. cartography/intel/slack/teams.py +65 -0
  220. cartography/intel/slack/users.py +57 -0
  221. cartography/intel/slack/utils.py +29 -0
  222. cartography/intel/spacelift/__init__.py +161 -0
  223. cartography/intel/spacelift/account.py +73 -0
  224. cartography/intel/spacelift/ec2_ownership.py +280 -0
  225. cartography/intel/spacelift/runs.py +463 -0
  226. cartography/intel/spacelift/spaces.py +112 -0
  227. cartography/intel/spacelift/stacks.py +119 -0
  228. cartography/intel/spacelift/util.py +122 -0
  229. cartography/intel/spacelift/workerpools.py +131 -0
  230. cartography/intel/spacelift/workers.py +128 -0
  231. cartography/intel/trivy/__init__.py +272 -0
  232. cartography/intel/trivy/scanner.py +386 -0
  233. cartography/models/airbyte/__init__.py +0 -0
  234. cartography/models/airbyte/connection.py +138 -0
  235. cartography/models/airbyte/destination.py +75 -0
  236. cartography/models/airbyte/organization.py +19 -0
  237. cartography/models/airbyte/source.py +75 -0
  238. cartography/models/airbyte/stream.py +74 -0
  239. cartography/models/airbyte/tag.py +69 -0
  240. cartography/models/airbyte/user.py +115 -0
  241. cartography/models/airbyte/workspace.py +46 -0
  242. cartography/models/anthropic/apikey.py +4 -0
  243. cartography/models/anthropic/user.py +4 -0
  244. cartography/models/aws/acm/__init__.py +0 -0
  245. cartography/models/aws/acm/certificate.py +75 -0
  246. cartography/models/aws/apigateway/__init__.py +0 -0
  247. cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
  248. cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
  249. cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
  250. cartography/models/aws/apigatewayv2/__init__.py +0 -0
  251. cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
  252. cartography/models/aws/cloudtrail/management_events.py +153 -0
  253. cartography/models/aws/cloudtrail/trail.py +45 -0
  254. cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
  255. cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
  256. cartography/models/aws/codebuild/__init__.py +0 -0
  257. cartography/models/aws/codebuild/project.py +49 -0
  258. cartography/models/aws/cognito/__init__.py +0 -0
  259. cartography/models/aws/cognito/identity_pool.py +70 -0
  260. cartography/models/aws/cognito/user_pool.py +47 -0
  261. cartography/models/aws/dynamodb/tables.py +2 -0
  262. cartography/models/aws/ec2/instances.py +25 -1
  263. cartography/models/aws/ec2/networkinterfaces.py +4 -0
  264. cartography/models/aws/ec2/security_group_rules.py +109 -0
  265. cartography/models/aws/ec2/security_groups.py +90 -0
  266. cartography/models/aws/ec2/snapshots.py +58 -0
  267. cartography/models/aws/ec2/subnet_instance.py +2 -0
  268. cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
  269. cartography/models/aws/ec2/subnets.py +65 -0
  270. cartography/models/aws/ec2/volumes.py +20 -0
  271. cartography/models/aws/ec2/vpc.py +46 -0
  272. cartography/models/aws/ec2/vpc_cidr.py +102 -0
  273. cartography/models/aws/ec2/vpc_peering.py +157 -0
  274. cartography/models/aws/ecr/__init__.py +0 -0
  275. cartography/models/aws/ecr/image.py +146 -0
  276. cartography/models/aws/ecr/image_layer.py +107 -0
  277. cartography/models/aws/ecr/repository.py +72 -0
  278. cartography/models/aws/ecr/repository_image.py +95 -0
  279. cartography/models/aws/ecs/__init__.py +0 -0
  280. cartography/models/aws/ecs/clusters.py +64 -0
  281. cartography/models/aws/ecs/container_definitions.py +93 -0
  282. cartography/models/aws/ecs/container_instances.py +84 -0
  283. cartography/models/aws/ecs/containers.py +101 -0
  284. cartography/models/aws/ecs/services.py +134 -0
  285. cartography/models/aws/ecs/task_definitions.py +135 -0
  286. cartography/models/aws/ecs/tasks.py +134 -0
  287. cartography/models/aws/efs/access_point.py +77 -0
  288. cartography/models/aws/efs/file_system.py +60 -0
  289. cartography/models/aws/efs/mount_target.py +29 -2
  290. cartography/models/aws/elasticache/__init__.py +0 -0
  291. cartography/models/aws/elasticache/cluster.py +65 -0
  292. cartography/models/aws/elasticache/topic.py +67 -0
  293. cartography/models/aws/eventbridge/__init__.py +0 -0
  294. cartography/models/aws/eventbridge/rule.py +77 -0
  295. cartography/models/aws/eventbridge/target.py +71 -0
  296. cartography/models/aws/glue/__init__.py +0 -0
  297. cartography/models/aws/glue/connection.py +51 -0
  298. cartography/models/aws/glue/job.py +69 -0
  299. cartography/models/aws/guardduty/__init__.py +1 -0
  300. cartography/models/aws/guardduty/detectors.py +50 -0
  301. cartography/models/aws/guardduty/findings.py +121 -0
  302. cartography/models/aws/iam/access_key.py +103 -0
  303. cartography/models/aws/iam/account_role.py +24 -0
  304. cartography/models/aws/iam/federated_principal.py +60 -0
  305. cartography/models/aws/iam/group.py +60 -0
  306. cartography/models/aws/iam/group_membership.py +27 -0
  307. cartography/models/aws/iam/inline_policy.py +78 -0
  308. cartography/models/aws/iam/managed_policy.py +51 -0
  309. cartography/models/aws/iam/policy_statement.py +57 -0
  310. cartography/models/aws/iam/role.py +83 -0
  311. cartography/models/aws/iam/root_principal.py +52 -0
  312. cartography/models/aws/iam/service_principal.py +30 -0
  313. cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
  314. cartography/models/aws/iam/user.py +59 -0
  315. cartography/models/aws/identitycenter/awsidentitycenter.py +1 -0
  316. cartography/models/aws/identitycenter/awspermissionset.py +70 -0
  317. cartography/models/aws/identitycenter/awssogroup.py +70 -0
  318. cartography/models/aws/identitycenter/awsssouser.py +49 -9
  319. cartography/models/aws/inspector/findings.py +37 -0
  320. cartography/models/aws/inspector/packages.py +1 -31
  321. cartography/models/aws/kms/__init__.py +0 -0
  322. cartography/models/aws/kms/aliases.py +86 -0
  323. cartography/models/aws/kms/grants.py +65 -0
  324. cartography/models/aws/kms/keys.py +88 -0
  325. cartography/models/aws/lambda_function/__init__.py +0 -0
  326. cartography/models/aws/lambda_function/alias.py +74 -0
  327. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  328. cartography/models/aws/lambda_function/lambda_function.py +91 -0
  329. cartography/models/aws/lambda_function/layer.py +72 -0
  330. cartography/models/aws/rds/__init__.py +0 -0
  331. cartography/models/aws/rds/cluster.py +91 -0
  332. cartography/models/aws/rds/event_subscription.py +146 -0
  333. cartography/models/aws/rds/instance.py +156 -0
  334. cartography/models/aws/rds/snapshot.py +108 -0
  335. cartography/models/aws/rds/subnet_group.py +101 -0
  336. cartography/models/aws/route53/__init__.py +0 -0
  337. cartography/models/aws/route53/dnsrecord.py +235 -0
  338. cartography/models/aws/route53/nameserver.py +63 -0
  339. cartography/models/aws/route53/subzone.py +40 -0
  340. cartography/models/aws/route53/zone.py +47 -0
  341. cartography/models/aws/s3/notification.py +24 -0
  342. cartography/models/aws/secretsmanager/secret.py +106 -0
  343. cartography/models/aws/secretsmanager/secret_version.py +0 -2
  344. cartography/models/aws/sns/topic_subscription.py +74 -0
  345. cartography/models/aws/sqs/__init__.py +0 -0
  346. cartography/models/aws/sqs/queue.py +89 -0
  347. cartography/models/azure/__init__.py +0 -0
  348. cartography/models/azure/aks_cluster.py +54 -0
  349. cartography/models/azure/aks_nodepool.py +54 -0
  350. cartography/models/azure/app_service.py +59 -0
  351. cartography/models/azure/container_instance.py +57 -0
  352. cartography/models/azure/cosmosdb/__init__.py +0 -0
  353. cartography/models/azure/cosmosdb/account.py +77 -0
  354. cartography/models/azure/cosmosdb/accountfailoverpolicy.py +77 -0
  355. cartography/models/azure/cosmosdb/cassandrakeyspace.py +82 -0
  356. cartography/models/azure/cosmosdb/cassandratable.py +81 -0
  357. cartography/models/azure/cosmosdb/corspolicy.py +74 -0
  358. cartography/models/azure/cosmosdb/dblocation.py +120 -0
  359. cartography/models/azure/cosmosdb/mongodbcollection.py +82 -0
  360. cartography/models/azure/cosmosdb/mongodbdatabase.py +78 -0
  361. cartography/models/azure/cosmosdb/privateendpointconnection.py +81 -0
  362. cartography/models/azure/cosmosdb/sqlcontainer.py +88 -0
  363. cartography/models/azure/cosmosdb/sqldatabase.py +78 -0
  364. cartography/models/azure/cosmosdb/tableresource.py +76 -0
  365. cartography/models/azure/cosmosdb/virtualnetworkrule.py +78 -0
  366. cartography/models/azure/data_factory/__init__.py +0 -0
  367. cartography/models/azure/data_factory/data_factory.py +51 -0
  368. cartography/models/azure/data_factory/data_factory_dataset.py +94 -0
  369. cartography/models/azure/data_factory/data_factory_linked_service.py +78 -0
  370. cartography/models/azure/data_factory/data_factory_pipeline.py +93 -0
  371. cartography/models/azure/data_lake_filesystem.py +51 -0
  372. cartography/models/azure/event_grid_topic.py +57 -0
  373. cartography/models/azure/function_app.py +59 -0
  374. cartography/models/azure/load_balancer/__init__.py +0 -0
  375. cartography/models/azure/load_balancer/load_balancer.py +49 -0
  376. cartography/models/azure/load_balancer/load_balancer_backend_pool.py +73 -0
  377. cartography/models/azure/load_balancer/load_balancer_frontend_ip.py +75 -0
  378. cartography/models/azure/load_balancer/load_balancer_inbound_nat_rule.py +78 -0
  379. cartography/models/azure/load_balancer/load_balancer_rule.py +108 -0
  380. cartography/models/azure/logic_apps.py +56 -0
  381. cartography/models/azure/monitor.py +54 -0
  382. cartography/models/azure/network_interface.py +112 -0
  383. cartography/models/azure/network_security_group.py +50 -0
  384. cartography/models/azure/permission_relationships.py +60 -0
  385. cartography/models/azure/principal.py +41 -0
  386. cartography/models/azure/public_ip_address.py +50 -0
  387. cartography/models/azure/rbac.py +268 -0
  388. cartography/models/azure/resource_groups.py +52 -0
  389. cartography/models/azure/security_center.py +50 -0
  390. cartography/models/azure/sql/__init__.py +0 -0
  391. cartography/models/azure/sql/databasethreatdetectionpolicy.py +85 -0
  392. cartography/models/azure/sql/elasticpool.py +77 -0
  393. cartography/models/azure/sql/failovergroup.py +73 -0
  394. cartography/models/azure/sql/recoverabledatabase.py +75 -0
  395. cartography/models/azure/sql/replicationlink.py +81 -0
  396. cartography/models/azure/sql/restorabledroppeddatabase.py +82 -0
  397. cartography/models/azure/sql/restorepoint.py +74 -0
  398. cartography/models/azure/sql/serveradadministrator.py +74 -0
  399. cartography/models/azure/sql/serverdnsalias.py +71 -0
  400. cartography/models/azure/sql/sqldatabase.py +85 -0
  401. cartography/models/azure/sql/sqlserver.py +50 -0
  402. cartography/models/azure/sql/transparentdataencryption.py +76 -0
  403. cartography/models/azure/storage/__init__.py +0 -0
  404. cartography/models/azure/storage/account.py +59 -0
  405. cartography/models/azure/storage/blobcontainer.py +85 -0
  406. cartography/models/azure/storage/blobservice.py +71 -0
  407. cartography/models/azure/storage/fileservice.py +71 -0
  408. cartography/models/azure/storage/fileshare.py +82 -0
  409. cartography/models/azure/storage/queue.py +71 -0
  410. cartography/models/azure/storage/queueservice.py +73 -0
  411. cartography/models/azure/storage/table.py +72 -0
  412. cartography/models/azure/storage/tableservice.py +73 -0
  413. cartography/models/azure/subnet.py +101 -0
  414. cartography/models/azure/subscription.py +47 -0
  415. cartography/models/azure/tags/__init__.py +0 -0
  416. cartography/models/azure/tags/storage_tag.py +40 -0
  417. cartography/models/azure/tags/tag.py +37 -0
  418. cartography/models/azure/tenant.py +17 -0
  419. cartography/models/azure/virtual_network.py +49 -0
  420. cartography/models/azure/vm/__init__.py +0 -0
  421. cartography/models/azure/vm/datadisk.py +80 -0
  422. cartography/models/azure/vm/disk.py +55 -0
  423. cartography/models/azure/vm/snapshot.py +56 -0
  424. cartography/models/azure/vm/virtualmachine.py +59 -0
  425. cartography/models/bigfix/bigfix_computer.py +1 -1
  426. cartography/models/cloudflare/member.py +4 -0
  427. cartography/models/core/common.py +1 -0
  428. cartography/models/core/nodes.py +15 -2
  429. cartography/models/core/relationships.py +44 -0
  430. cartography/models/crowdstrike/hosts.py +1 -1
  431. cartography/models/digitalocean/droplet.py +2 -0
  432. cartography/models/duo/endpoint.py +1 -1
  433. cartography/models/duo/phone.py +2 -2
  434. cartography/models/duo/user.py +4 -0
  435. cartography/models/entra/app_role_assignment.py +115 -0
  436. cartography/models/entra/application.py +49 -0
  437. cartography/models/entra/entra_user_to_aws_sso.py +41 -0
  438. cartography/models/entra/group.py +117 -0
  439. cartography/models/entra/service_principal.py +104 -0
  440. cartography/models/entra/user.py +42 -51
  441. cartography/models/gcp/__init__.py +0 -0
  442. cartography/models/gcp/bigtable/__init__.py +0 -0
  443. cartography/models/gcp/bigtable/app_profile.py +94 -0
  444. cartography/models/gcp/bigtable/backup.py +91 -0
  445. cartography/models/gcp/bigtable/cluster.py +73 -0
  446. cartography/models/gcp/bigtable/instance.py +52 -0
  447. cartography/models/gcp/bigtable/table.py +69 -0
  448. cartography/models/gcp/compute/__init__.py +0 -0
  449. cartography/models/gcp/compute/subnet.py +74 -0
  450. cartography/models/gcp/compute/vpc.py +50 -0
  451. cartography/models/gcp/crm/__init__.py +0 -0
  452. cartography/models/gcp/crm/folders.py +98 -0
  453. cartography/models/gcp/crm/organizations.py +21 -0
  454. cartography/models/gcp/crm/projects.py +100 -0
  455. cartography/models/gcp/dns.py +109 -0
  456. cartography/models/gcp/gke.py +69 -0
  457. cartography/models/gcp/iam.py +3 -0
  458. cartography/models/gcp/permission_relationships.py +61 -0
  459. cartography/models/gcp/policy_bindings.py +93 -0
  460. cartography/models/gcp/storage/__init__.py +0 -0
  461. cartography/models/gcp/storage/bucket.py +119 -0
  462. cartography/models/github/commits.py +63 -0
  463. cartography/models/github/dependencies.py +73 -0
  464. cartography/models/github/manifests.py +49 -0
  465. cartography/models/github/users.py +10 -0
  466. cartography/models/googleworkspace/__init__.py +0 -0
  467. cartography/models/googleworkspace/device.py +132 -0
  468. cartography/models/googleworkspace/group.py +382 -0
  469. cartography/models/googleworkspace/oauth_app.py +124 -0
  470. cartography/models/googleworkspace/tenant.py +30 -0
  471. cartography/models/googleworkspace/user.py +113 -0
  472. cartography/models/gsuite/__init__.py +0 -0
  473. cartography/models/gsuite/group.py +218 -0
  474. cartography/models/gsuite/tenant.py +29 -0
  475. cartography/models/gsuite/user.py +107 -0
  476. cartography/models/kandji/device.py +1 -2
  477. cartography/models/keycloak/__init__.py +0 -0
  478. cartography/models/keycloak/authenticationexecution.py +160 -0
  479. cartography/models/keycloak/authenticationflow.py +54 -0
  480. cartography/models/keycloak/client.py +179 -0
  481. cartography/models/keycloak/group.py +101 -0
  482. cartography/models/keycloak/identityprovider.py +89 -0
  483. cartography/models/keycloak/organization.py +116 -0
  484. cartography/models/keycloak/organizationdomain.py +73 -0
  485. cartography/models/keycloak/realm.py +173 -0
  486. cartography/models/keycloak/role.py +126 -0
  487. cartography/models/keycloak/scope.py +73 -0
  488. cartography/models/keycloak/user.py +55 -0
  489. cartography/models/kubernetes/__init__.py +0 -0
  490. cartography/models/kubernetes/clusterrolebindings.py +138 -0
  491. cartography/models/kubernetes/clusterroles.py +52 -0
  492. cartography/models/kubernetes/clusters.py +26 -0
  493. cartography/models/kubernetes/containers.py +133 -0
  494. cartography/models/kubernetes/groups.py +107 -0
  495. cartography/models/kubernetes/namespaces.py +51 -0
  496. cartography/models/kubernetes/oidc.py +51 -0
  497. cartography/models/kubernetes/pods.py +80 -0
  498. cartography/models/kubernetes/rolebindings.py +159 -0
  499. cartography/models/kubernetes/roles.py +76 -0
  500. cartography/models/kubernetes/secrets.py +79 -0
  501. cartography/models/kubernetes/serviceaccounts.py +77 -0
  502. cartography/models/kubernetes/services.py +108 -0
  503. cartography/models/kubernetes/users.py +105 -0
  504. cartography/models/lastpass/user.py +4 -0
  505. cartography/models/ontology/__init__.py +0 -0
  506. cartography/models/ontology/device.py +137 -0
  507. cartography/models/ontology/mapping/__init__.py +76 -0
  508. cartography/models/ontology/mapping/data/__init__.py +0 -0
  509. cartography/models/ontology/mapping/data/apikeys.py +93 -0
  510. cartography/models/ontology/mapping/data/computeinstance.py +95 -0
  511. cartography/models/ontology/mapping/data/containers.py +88 -0
  512. cartography/models/ontology/mapping/data/databases.py +182 -0
  513. cartography/models/ontology/mapping/data/devices.py +194 -0
  514. cartography/models/ontology/mapping/data/thirdpartyapps.py +140 -0
  515. cartography/models/ontology/mapping/data/useraccounts.py +416 -0
  516. cartography/models/ontology/mapping/data/users.py +63 -0
  517. cartography/models/ontology/mapping/specs.py +85 -0
  518. cartography/models/ontology/user.py +51 -0
  519. cartography/models/openai/adminapikey.py +4 -0
  520. cartography/models/openai/apikey.py +4 -0
  521. cartography/models/openai/user.py +4 -0
  522. cartography/models/scaleway/__init__.py +0 -0
  523. cartography/models/scaleway/iam/__init__.py +0 -0
  524. cartography/models/scaleway/iam/apikey.py +100 -0
  525. cartography/models/scaleway/iam/application.py +52 -0
  526. cartography/models/scaleway/iam/group.py +95 -0
  527. cartography/models/scaleway/iam/user.py +64 -0
  528. cartography/models/scaleway/instance/__init__.py +0 -0
  529. cartography/models/scaleway/instance/flexibleip.py +52 -0
  530. cartography/models/scaleway/instance/instance.py +120 -0
  531. cartography/models/scaleway/organization.py +19 -0
  532. cartography/models/scaleway/project.py +48 -0
  533. cartography/models/scaleway/storage/__init__.py +0 -0
  534. cartography/models/scaleway/storage/snapshot.py +78 -0
  535. cartography/models/scaleway/storage/volume.py +51 -0
  536. cartography/models/sentinelone/__init__.py +1 -0
  537. cartography/models/sentinelone/account.py +40 -0
  538. cartography/models/sentinelone/agent.py +50 -0
  539. cartography/models/sentinelone/application.py +44 -0
  540. cartography/models/sentinelone/application_version.py +96 -0
  541. cartography/models/sentinelone/cve.py +73 -0
  542. cartography/models/slack/__init__.py +0 -0
  543. cartography/models/slack/channels.py +92 -0
  544. cartography/models/slack/group.py +129 -0
  545. cartography/models/slack/team.py +22 -0
  546. cartography/models/slack/user.py +62 -0
  547. cartography/models/snipeit/asset.py +2 -0
  548. cartography/models/snipeit/user.py +4 -0
  549. cartography/models/spacelift/__init__.py +0 -0
  550. cartography/models/spacelift/cloudtrailevent.py +120 -0
  551. cartography/models/spacelift/run.py +162 -0
  552. cartography/models/spacelift/space.py +131 -0
  553. cartography/models/spacelift/spaceliftaccount.py +31 -0
  554. cartography/models/spacelift/spaceliftgitcommit.py +157 -0
  555. cartography/models/spacelift/stack.py +96 -0
  556. cartography/models/spacelift/user.py +63 -0
  557. cartography/models/spacelift/worker.py +97 -0
  558. cartography/models/spacelift/workerpool.py +90 -0
  559. cartography/models/tailscale/device.py +2 -1
  560. cartography/models/tailscale/user.py +6 -1
  561. cartography/models/trivy/__init__.py +0 -0
  562. cartography/models/trivy/findings.py +66 -0
  563. cartography/models/trivy/fix.py +66 -0
  564. cartography/models/trivy/package.py +71 -0
  565. cartography/rules/README.md +1 -0
  566. cartography/rules/__init__.py +0 -0
  567. cartography/rules/cli.py +261 -0
  568. cartography/rules/data/__init__.py +0 -0
  569. cartography/rules/data/rules/__init__.py +46 -0
  570. cartography/rules/data/rules/cloud_security_product_deactivated.py +49 -0
  571. cartography/rules/data/rules/compute_instance_exposed.py +51 -0
  572. cartography/rules/data/rules/database_instance_exposed.py +53 -0
  573. cartography/rules/data/rules/delegation_boundary_modifiable.py +90 -0
  574. cartography/rules/data/rules/identity_administration_privileges.py +100 -0
  575. cartography/rules/data/rules/inactive_user_active_accounts.py +48 -0
  576. cartography/rules/data/rules/malicious_npm_dependencies_shai_hulud.py +2222 -0
  577. cartography/rules/data/rules/mfa_missing.py +46 -0
  578. cartography/rules/data/rules/object_storage_public.py +100 -0
  579. cartography/rules/data/rules/policy_administration_privileges.py +104 -0
  580. cartography/rules/data/rules/unmanaged_accounts.py +43 -0
  581. cartography/rules/data/rules/workload_identity_admin_capabilities.py +193 -0
  582. cartography/rules/formatters.py +108 -0
  583. cartography/rules/runners.py +216 -0
  584. cartography/rules/spec/__init__.py +0 -0
  585. cartography/rules/spec/model.py +267 -0
  586. cartography/rules/spec/result.py +38 -0
  587. cartography/sync.py +25 -5
  588. cartography/util.py +101 -31
  589. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/METADATA +61 -22
  590. cartography-0.123.0.dist-info/RECORD +856 -0
  591. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
  592. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  593. cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
  594. cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json +0 -24
  595. cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
  596. cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
  597. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  598. cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
  599. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  600. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  601. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  602. cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
  603. cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
  604. cartography/data/jobs/cleanup/aws_import_snapshots_cleanup.json +0 -30
  605. cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
  606. cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
  607. cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
  608. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  609. cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json +0 -25
  610. cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json +0 -15
  611. cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json +0 -25
  612. cartography/data/jobs/cleanup/azure_cosmosdb_sql_database_cleanup.json +0 -25
  613. cartography/data/jobs/cleanup/azure_cosmosdb_table_resources_cleanup.json +0 -15
  614. cartography/data/jobs/cleanup/azure_database_account_cleanup.json +0 -85
  615. cartography/data/jobs/cleanup/azure_import_disks_cleanup.json +0 -15
  616. cartography/data/jobs/cleanup/azure_import_snapshots_cleanup.json +0 -15
  617. cartography/data/jobs/cleanup/azure_import_virtual_machines_cleanup.json +0 -25
  618. cartography/data/jobs/cleanup/azure_sql_server_cleanup.json +0 -125
  619. cartography/data/jobs/cleanup/azure_storage_account_cleanup.json +0 -95
  620. cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json +0 -14
  621. cartography/data/jobs/cleanup/azure_tenant_cleanup.json +0 -9
  622. cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
  623. cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
  624. cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
  625. cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
  626. cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
  627. cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
  628. cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
  629. cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
  630. cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
  631. cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
  632. cartography/intel/gcp/crm.py +0 -355
  633. cartography/intel/gsuite/api.py +0 -342
  634. cartography-0.104.0rc2.dist-info/RECORD +0 -455
  635. /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
  636. /cartography/models/aws/{apigateway.py → apigateway/apigateway.py} +0 -0
  637. /cartography/models/aws/{apigatewaycertificate.py → apigateway/apigatewaycertificate.py} +0 -0
  638. /cartography/models/aws/{apigatewayresource.py → apigateway/apigatewayresource.py} +0 -0
  639. /cartography/models/aws/{apigatewaystage.py → apigateway/apigatewaystage.py} +0 -0
  640. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/WHEEL +0 -0
  641. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/licenses/LICENSE +0 -0
  642. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,8 @@
1
1
  import logging
2
2
  from dataclasses import asdict
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version
3
5
  from string import Template
4
- from typing import Dict
5
- from typing import List
6
- from typing import Optional
7
- from typing import Set
8
- from typing import Tuple
9
6
 
10
7
  from cartography.models.core.common import PropertyRef
11
8
  from cartography.models.core.nodes import CartographyNodeProperties
@@ -14,21 +11,319 @@ from cartography.models.core.nodes import ExtraNodeLabels
14
11
  from cartography.models.core.relationships import CartographyRelSchema
15
12
  from cartography.models.core.relationships import LinkDirection
16
13
  from cartography.models.core.relationships import OtherRelationships
14
+ from cartography.models.core.relationships import SourceNodeMatcher
17
15
  from cartography.models.core.relationships import TargetNodeMatcher
16
+ from cartography.models.ontology.mapping import (
17
+ get_semantic_label_mapping_from_node_schema,
18
+ )
19
+ from cartography.models.ontology.mapping.specs import OntologyFieldMapping
18
20
 
19
21
  logger = logging.getLogger(__name__)
20
22
 
21
23
 
24
+ def _build_ontology_field_statement_invert_boolean(
25
+ mapping_field: OntologyFieldMapping,
26
+ property_ref: PropertyRef,
27
+ ) -> str:
28
+ # toBooleanOrNull will return a boolean or null if it can't be converted
29
+ # coalesce will return the first non-null value, so if toBooleanOrNull returns null,
30
+ # we invert the boolean value of the property_ref existence
31
+ # ex: "false", "0", "no" => false; anything else => true; null/absent => true
32
+ invert_boolean_template = Template(
33
+ "i.$node_property = (NOT(coalesce(toBooleanOrNull($property_ref), false)))"
34
+ )
35
+ return invert_boolean_template.safe_substitute(
36
+ node_property=f"_ont_{mapping_field.ontology_field}",
37
+ property_ref=property_ref,
38
+ )
39
+
40
+
41
+ def _build_ontology_field_statement_to_boolean(
42
+ mapping_field: OntologyFieldMapping,
43
+ property_ref: PropertyRef,
44
+ ) -> str:
45
+ # toBoleanOrNull will return a boolean or null if it can't be converted
46
+ # coalesce will return the first non-null value, so if toBooleanOrNull returns null,
47
+ # it will return whether the property_ref is not null (i.e., true if property_ref exists)
48
+ # this way, any non-null value is treated as true
49
+ # ex: "true", "1", "yes" => true; anything else => true; null/absent => false
50
+ to_boolean_template = Template(
51
+ "i.$node_property = coalesce(toBooleanOrNull($property_ref), ($property_ref IS NOT NULL))"
52
+ )
53
+ return to_boolean_template.safe_substitute(
54
+ node_property=f"_ont_{mapping_field.ontology_field}",
55
+ property_ref=property_ref,
56
+ )
57
+
58
+
59
+ def _build_ontology_field_statement_equal_boolean(
60
+ mapping_field: OntologyFieldMapping,
61
+ property_ref: PropertyRef,
62
+ ) -> str | None:
63
+ # we check if the property_ref is in the list of expected boolean values
64
+ equal_boolean_template = Template(
65
+ "i.$node_property = ($property_ref IN $property_values)"
66
+ )
67
+ extra_field_values = mapping_field.extra.get("values")
68
+ if extra_field_values is None:
69
+ # should not occure due to unit test but failing gracefully
70
+ logger.warning(
71
+ "equal_boolean special handling requires 'values' in extra for field %s",
72
+ mapping_field.ontology_field,
73
+ )
74
+ return None
75
+ if not isinstance(extra_field_values, list):
76
+ logger.warning(
77
+ "equal_boolean special handling 'values' in extra for field %s must be a list",
78
+ mapping_field.ontology_field,
79
+ )
80
+ return None
81
+ return equal_boolean_template.substitute(
82
+ node_property=f"_ont_{mapping_field.ontology_field}",
83
+ property_ref=property_ref,
84
+ property_values=extra_field_values,
85
+ )
86
+
87
+
88
+ def _escape_cypher_string(value: str) -> str:
89
+ r"""
90
+ Escape special characters in a string value for use in a Cypher string literal.
91
+
92
+ In Cypher, string literals are enclosed in double quotes, and the following characters
93
+ must be escaped with a backslash:
94
+ - Backslash (\) -> \\
95
+ - Double quote (") -> \"
96
+
97
+ :param value: The string value to escape
98
+ :return: The escaped string value safe for use in a Cypher string literal
99
+ """
100
+ # Escape backslashes first (must be done before escaping quotes)
101
+ escaped = value.replace("\\", "\\\\")
102
+ # Then escape double quotes
103
+ escaped = escaped.replace('"', '\\"')
104
+ return escaped
105
+
106
+
107
+ def _build_ontology_field_statement_static_value(
108
+ mapping_field: OntologyFieldMapping,
109
+ ) -> str | None:
110
+ # Sets a static value for the ontology field
111
+ # The value is provided in extra['value']
112
+ static_value_template = Template("i.$node_property = $static_value")
113
+ extra_value = mapping_field.extra.get("value")
114
+ if extra_value is None:
115
+ # should not occur due to unit test but failing gracefully
116
+ logger.warning(
117
+ "static_value special handling requires 'value' in extra for field %s",
118
+ mapping_field.ontology_field,
119
+ )
120
+ return None
121
+
122
+ # Format the value appropriately for Cypher
123
+ if isinstance(extra_value, str):
124
+ formatted_value = f'"{_escape_cypher_string(extra_value)}"'
125
+ elif isinstance(extra_value, bool):
126
+ formatted_value = str(extra_value).lower()
127
+ else:
128
+ formatted_value = str(extra_value)
129
+
130
+ return static_value_template.substitute(
131
+ node_property=f"_ont_{mapping_field.ontology_field}",
132
+ static_value=formatted_value,
133
+ )
134
+
135
+
136
+ def _build_ontology_field_statement_or_boolean(
137
+ mapping_field: OntologyFieldMapping,
138
+ node_property_map: dict[str, PropertyRef],
139
+ ) -> str | None:
140
+ # The or_clause is needed to avoid comparing nulls to boolean values
141
+ # See: https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/#cypher-null-logical-operators
142
+ or_clause = Template("coalesce(toBooleanOrNull($property_ref), false)")
143
+ or_boolean_template = Template("i.$node_property = ($property_condition)")
144
+ extra_fields = mapping_field.extra.get("fields")
145
+ if extra_fields is None:
146
+ # should not occure due to unit test but failing gracefully
147
+ logger.warning(
148
+ "or_boolean special handling requires 'fields' in extra for field %s",
149
+ mapping_field.ontology_field,
150
+ )
151
+ return None
152
+ if not isinstance(extra_fields, list):
153
+ # should not occure due to unit test but failing gracefully
154
+ logger.warning(
155
+ "or_boolean special handling 'fields' in extra for field %s must be a list",
156
+ mapping_field.ontology_field,
157
+ )
158
+ return None
159
+
160
+ property_conditions = [
161
+ or_clause.substitute(
162
+ property_ref=node_property_map.get(mapping_field.node_field),
163
+ )
164
+ ]
165
+ for extra_field in mapping_field.extra.get("fields", []):
166
+ extra_property_ref = node_property_map.get(extra_field)
167
+ if not extra_property_ref:
168
+ # should not occure due to unit test but failing gracefully
169
+ logger.warning(
170
+ "Extra field '%s' not found in node properties for or_boolean special handling of field %s",
171
+ extra_field,
172
+ mapping_field.ontology_field,
173
+ )
174
+ continue
175
+ property_conditions.append(
176
+ or_clause.substitute(
177
+ property_ref=extra_property_ref,
178
+ )
179
+ )
180
+ full_property_condition = " OR ".join(property_conditions)
181
+ return or_boolean_template.substitute(
182
+ node_property=f"_ont_{mapping_field.ontology_field}",
183
+ property_condition=full_property_condition,
184
+ )
185
+
186
+
187
+ def _build_ontology_field_statement_nor_boolean(
188
+ mapping_field: OntologyFieldMapping,
189
+ node_property_map: dict[str, PropertyRef],
190
+ ) -> str | None:
191
+ nor_clause = Template("NOT(coalesce(toBooleanOrNull($property_ref), false))")
192
+ nor_boolean_template = Template("i.$node_property = ($property_condition)")
193
+ extra_fields = mapping_field.extra.get("fields")
194
+ if extra_fields is None:
195
+ # should not occure due to unit test but failing gracefully
196
+ logger.warning(
197
+ "nor_boolean special handling requires 'fields' in extra for field %s",
198
+ mapping_field.ontology_field,
199
+ )
200
+ return None
201
+ if not isinstance(extra_fields, list):
202
+ # should not occure due to unit test but failing gracefully
203
+ logger.warning(
204
+ "nor_boolean special handling 'fields' in extra for field %s must be a list",
205
+ mapping_field.ontology_field,
206
+ )
207
+ return None
208
+
209
+ property_conditions = [
210
+ nor_clause.substitute(
211
+ property_ref=node_property_map.get(mapping_field.node_field),
212
+ )
213
+ ]
214
+ for extra_field in mapping_field.extra.get("fields", []):
215
+ extra_property_ref = node_property_map.get(extra_field)
216
+ if not extra_property_ref:
217
+ # should not occure due to unit test but failing gracefully
218
+ logger.warning(
219
+ "Extra field '%s' not found in node properties for nor_boolean special handling of field %s",
220
+ extra_field,
221
+ mapping_field.ontology_field,
222
+ )
223
+ continue
224
+ property_conditions.append(
225
+ nor_clause.substitute(
226
+ property_ref=extra_property_ref,
227
+ )
228
+ )
229
+ full_property_condition = " AND ".join(property_conditions)
230
+ return nor_boolean_template.substitute(
231
+ node_property=f"_ont_{mapping_field.ontology_field}",
232
+ property_condition=full_property_condition,
233
+ )
234
+
235
+
236
+ def _build_ontology_node_properties_statement(
237
+ node_schema: CartographyNodeSchema,
238
+ node_property_map: dict[str, PropertyRef],
239
+ ) -> str:
240
+ # DOC
241
+ # Try to get the mapping for the given node schema
242
+ ontology_mapping = get_semantic_label_mapping_from_node_schema(node_schema)
243
+ if not ontology_mapping:
244
+ return ""
245
+
246
+ source = _get_module_from_schema(node_schema).rsplit(":", maxsplit=1)[-1]
247
+ set_clauses = [f"i._ont_source = '{source}'"]
248
+ for mapping_field in ontology_mapping.fields:
249
+ ontology_field_name = f"_ont_{mapping_field.ontology_field}"
250
+ node_propertyref = node_property_map.get(mapping_field.node_field)
251
+
252
+ # Handle static_value special handling first - it doesn't require a node_field
253
+ if mapping_field.special_handling == "static_value":
254
+ static_value_statement = _build_ontology_field_statement_static_value(
255
+ mapping_field
256
+ )
257
+ if static_value_statement:
258
+ set_clauses.append(static_value_statement)
259
+ continue
260
+
261
+ # Skip validation for special_handling that don't require node_field
262
+ if not node_propertyref:
263
+ # This should not occure due to unit test but failing gracefully
264
+ logger.warning(
265
+ "Field '%s' not found in node properties for node schema %s",
266
+ mapping_field.node_field,
267
+ node_schema.__class__.__name__,
268
+ )
269
+ continue
270
+ if mapping_field.special_handling == "invert_boolean":
271
+ set_clauses.append(
272
+ _build_ontology_field_statement_invert_boolean(
273
+ mapping_field,
274
+ node_propertyref,
275
+ )
276
+ )
277
+ elif mapping_field.special_handling == "to_boolean":
278
+ set_clauses.append(
279
+ _build_ontology_field_statement_to_boolean(
280
+ mapping_field,
281
+ node_propertyref,
282
+ )
283
+ )
284
+ elif mapping_field.special_handling == "equal_boolean":
285
+ equal_boolean_statement = _build_ontology_field_statement_equal_boolean(
286
+ mapping_field,
287
+ node_propertyref,
288
+ )
289
+ if equal_boolean_statement:
290
+ set_clauses.append(equal_boolean_statement)
291
+ elif mapping_field.special_handling == "or_boolean":
292
+ or_boolean_statement = _build_ontology_field_statement_or_boolean(
293
+ mapping_field, node_property_map
294
+ )
295
+ if or_boolean_statement:
296
+ set_clauses.append(or_boolean_statement)
297
+ elif mapping_field.special_handling == "nor_boolean":
298
+ nor_boolean_statement = _build_ontology_field_statement_nor_boolean(
299
+ mapping_field, node_property_map
300
+ )
301
+ if nor_boolean_statement:
302
+ set_clauses.append(nor_boolean_statement)
303
+ else:
304
+ simple_field_template = Template("i.$node_property = $property_ref")
305
+ set_clauses.append(
306
+ simple_field_template.substitute(
307
+ node_property=ontology_field_name,
308
+ property_ref=node_propertyref,
309
+ )
310
+ )
311
+ if len(set_clauses) == 0:
312
+ return ""
313
+ # Add initial newline
314
+ return ",\n" + ",\n".join(set_clauses)
315
+
316
+
22
317
  def _build_node_properties_statement(
23
- node_property_map: Dict[str, PropertyRef],
24
- extra_node_labels: Optional[ExtraNodeLabels] = None,
318
+ node_property_map: dict[str, PropertyRef],
319
+ extra_node_labels: ExtraNodeLabels | None = None,
25
320
  ) -> str:
26
321
  """
27
322
  Generate a Neo4j clause that sets node properties using the given mapping of attribute names to PropertyRefs.
28
323
 
29
324
  As seen in this example,
30
325
 
31
- node_property_map: Dict[str, PropertyRef] = {
326
+ node_property_map: dict[str, PropertyRef] = {
32
327
  'id': PropertyRef("Id"),
33
328
  'node_prop_1': PropertyRef("Prop1"),
34
329
  'node_prop_2': PropertyRef("Prop2", set_in_kwargs=True),
@@ -69,7 +364,7 @@ def _build_node_properties_statement(
69
364
 
70
365
  def _build_rel_properties_statement(
71
366
  rel_var: str,
72
- rel_property_map: Optional[Dict[str, PropertyRef]] = None,
367
+ rel_property_map: dict[str, PropertyRef] | None = None,
73
368
  ) -> str:
74
369
  """
75
370
  Generate a Neo4j clause that sets relationship properties using the given mapping of attribute names to
@@ -77,7 +372,7 @@ def _build_rel_properties_statement(
77
372
 
78
373
  In this code example:
79
374
 
80
- rel_property_map: Dict[str, PropertyRef] = {
375
+ rel_property_map: dict[str, PropertyRef] = {
81
376
  'rel_prop_1': PropertyRef("Prop1"),
82
377
  'rel_prop_2': PropertyRef("Prop2", static=True),
83
378
  }
@@ -109,10 +404,10 @@ def _build_rel_properties_statement(
109
404
  return set_clause
110
405
 
111
406
 
112
- def _build_match_clause(matcher: TargetNodeMatcher) -> str:
407
+ def _build_match_clause(matcher: TargetNodeMatcher | SourceNodeMatcher) -> str:
113
408
  """
114
409
  Generate a Neo4j match statement on one or more keys and values for a given node.
115
- :param matcher: A TargetNodeMatcher object
410
+ :param matcher: A TargetNodeMatcher or SourceNodeMatcher object
116
411
  :return: a Neo4j match clause
117
412
  """
118
413
  match = Template("$Key: $PropRef")
@@ -173,13 +468,13 @@ def _build_where_clause_for_rel_match(
173
468
 
174
469
  def _asdict_with_validate_relprops(
175
470
  link: CartographyRelSchema,
176
- ) -> Dict[str, PropertyRef]:
471
+ ) -> dict[str, PropertyRef]:
177
472
  """
178
473
  Give a helpful error message when forgetting to put `()` when instantiating a CartographyRelSchema, as this
179
474
  isn't always caught by IDEs.
180
475
  """
181
476
  try:
182
- rel_props_as_dict: Dict[str, PropertyRef] = asdict(link.properties)
477
+ rel_props_as_dict: dict[str, PropertyRef] = asdict(link.properties)
183
478
  except TypeError as e:
184
479
  if (
185
480
  e.args
@@ -197,7 +492,7 @@ def _asdict_with_validate_relprops(
197
492
 
198
493
 
199
494
  def _build_attach_sub_resource_statement(
200
- sub_resource_link: Optional[CartographyRelSchema] = None,
495
+ sub_resource_link: CartographyRelSchema | None = None,
201
496
  ) -> str:
202
497
  """
203
498
  Generates a Neo4j statement to attach a sub resource to a node. A 'sub resource' is a term we made up to describe
@@ -222,6 +517,8 @@ def _build_attach_sub_resource_statement(
222
517
  $RelMergeClause
223
518
  ON CREATE SET r.firstseen = timestamp()
224
519
  SET
520
+ r._module_name = "$module_name",
521
+ r._module_version = "$module_version",
225
522
  $set_rel_properties_statement
226
523
  """,
227
524
  )
@@ -235,7 +532,7 @@ def _build_attach_sub_resource_statement(
235
532
  SubResourceRelLabel=sub_resource_link.rel_label,
236
533
  )
237
534
 
238
- rel_props_as_dict: Dict[str, PropertyRef] = _asdict_with_validate_relprops(
535
+ rel_props_as_dict: dict[str, PropertyRef] = _asdict_with_validate_relprops(
239
536
  sub_resource_link,
240
537
  )
241
538
 
@@ -243,6 +540,8 @@ def _build_attach_sub_resource_statement(
243
540
  SubResourceLabel=sub_resource_link.target_node_label,
244
541
  MatchClause=_build_match_clause(sub_resource_link.target_node_matcher),
245
542
  RelMergeClause=rel_merge_clause,
543
+ module_name=_get_module_from_schema(sub_resource_link),
544
+ module_version=_get_cartography_version(),
246
545
  SubResourceRelLabel=sub_resource_link.rel_label,
247
546
  set_rel_properties_statement=_build_rel_properties_statement(
248
547
  "r",
@@ -253,7 +552,7 @@ def _build_attach_sub_resource_statement(
253
552
 
254
553
 
255
554
  def _build_attach_additional_links_statement(
256
- additional_relationships: Optional[OtherRelationships] = None,
555
+ additional_relationships: OtherRelationships | None = None,
257
556
  ) -> str:
258
557
  """
259
558
  Generates a Neo4j statement to attach one or more CartographyRelSchemas to node(s) previously mentioned in the
@@ -277,6 +576,8 @@ def _build_attach_additional_links_statement(
277
576
  $RelMerge
278
577
  ON CREATE SET $rel_var.firstseen = timestamp()
279
578
  SET
579
+ $rel_var._module_name = "$module_name",
580
+ $rel_var._module_version = "$module_version",
280
581
  $set_rel_properties_statement
281
582
  """,
282
583
  )
@@ -311,6 +612,8 @@ def _build_attach_additional_links_statement(
311
612
  node_var=node_var,
312
613
  rel_var=rel_var,
313
614
  RelMerge=rel_merge,
615
+ module_name=_get_module_from_schema(link),
616
+ module_version=_get_cartography_version(),
314
617
  set_rel_properties_statement=_build_rel_properties_statement(
315
618
  rel_var,
316
619
  rel_props_as_dict,
@@ -322,8 +625,8 @@ def _build_attach_additional_links_statement(
322
625
 
323
626
 
324
627
  def _build_attach_relationships_statement(
325
- sub_resource_relationship: Optional[CartographyRelSchema],
326
- other_relationships: Optional[OtherRelationships],
628
+ sub_resource_relationship: CartographyRelSchema | None,
629
+ other_relationships: OtherRelationships | None,
327
630
  ) -> str:
328
631
  """
329
632
  Use Neo4j subqueries to attach sub resource and/or other relationships.
@@ -381,8 +684,8 @@ def rel_present_on_node_schema(
381
684
 
382
685
  def filter_selected_relationships(
383
686
  node_schema: CartographyNodeSchema,
384
- selected_relationships: Set[CartographyRelSchema],
385
- ) -> Tuple[Optional[CartographyRelSchema], Optional[OtherRelationships]]:
687
+ selected_relationships: set[CartographyRelSchema],
688
+ ) -> tuple[CartographyRelSchema | None, OtherRelationships | None]:
386
689
  """
387
690
  Ensures that selected relationships specified to build_ingestion_query() are actually present on
388
691
  node_schema.sub_resource_relationship and node_schema.other_relationships.
@@ -425,7 +728,7 @@ def filter_selected_relationships(
425
728
 
426
729
  def build_ingestion_query(
427
730
  node_schema: CartographyNodeSchema,
428
- selected_relationships: Optional[Set[CartographyRelSchema]] = None,
731
+ selected_relationships: set[CartographyRelSchema] | None = None,
429
732
  ) -> str:
430
733
  """
431
734
  Generates a Neo4j query from the given CartographyNodeSchema to ingest the specified nodes and relationships so that
@@ -452,19 +755,22 @@ def build_ingestion_query(
452
755
  MERGE (i:$node_label{id: $dict_id_field})
453
756
  ON CREATE SET i.firstseen = timestamp()
454
757
  SET
758
+ i._module_name = "$module_name",
759
+ i._module_version = "$module_version",
455
760
  $set_node_properties_statement
761
+ $set_ontology_node_properties_statement
456
762
  $attach_relationships_statement
457
763
  """,
458
764
  )
459
765
 
460
766
  node_props: CartographyNodeProperties = node_schema.properties
461
- node_props_as_dict: Dict[str, PropertyRef] = asdict(node_props)
767
+ node_props_as_dict: dict[str, PropertyRef] = asdict(node_props)
462
768
 
463
769
  # Handle selected relationships
464
- sub_resource_rel: Optional[CartographyRelSchema] = (
770
+ sub_resource_rel: CartographyRelSchema | None = (
465
771
  node_schema.sub_resource_relationship
466
772
  )
467
- other_rels: Optional[OtherRelationships] = node_schema.other_relationships
773
+ other_rels: OtherRelationships | None = node_schema.other_relationships
468
774
  if selected_relationships or selected_relationships == set():
469
775
  sub_resource_rel, other_rels = filter_selected_relationships(
470
776
  node_schema,
@@ -474,10 +780,16 @@ def build_ingestion_query(
474
780
  ingest_query = query_template.safe_substitute(
475
781
  node_label=node_schema.label,
476
782
  dict_id_field=node_props.id,
783
+ module_name=_get_module_from_schema(node_schema),
784
+ module_version=_get_cartography_version(),
477
785
  set_node_properties_statement=_build_node_properties_statement(
478
786
  node_props_as_dict,
479
787
  node_schema.extra_node_labels,
480
788
  ),
789
+ set_ontology_node_properties_statement=_build_ontology_node_properties_statement(
790
+ node_schema,
791
+ node_props_as_dict,
792
+ ),
481
793
  attach_relationships_statement=_build_attach_relationships_statement(
482
794
  sub_resource_rel,
483
795
  other_rels,
@@ -486,7 +798,7 @@ def build_ingestion_query(
486
798
  return ingest_query
487
799
 
488
800
 
489
- def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
801
+ def build_create_index_queries(node_schema: CartographyNodeSchema) -> list[str]:
490
802
  """
491
803
  Generate queries to create indexes for the given CartographyNodeSchema and all node types attached to it via its
492
804
  relationships.
@@ -536,7 +848,7 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
536
848
  )
537
849
 
538
850
  # Now, include extra indexes defined by the module author on the node schema's property refs.
539
- node_props_as_dict: Dict[str, PropertyRef] = asdict(node_schema.properties)
851
+ node_props_as_dict: dict[str, PropertyRef] = asdict(node_schema.properties)
540
852
  result.extend(
541
853
  [
542
854
  index_template.safe_substitute(
@@ -548,3 +860,192 @@ def build_create_index_queries(node_schema: CartographyNodeSchema) -> List[str]:
548
860
  ],
549
861
  )
550
862
  return result
863
+
864
+
865
+ def build_create_index_queries_for_matchlink(
866
+ rel_schema: CartographyRelSchema,
867
+ ) -> list[str]:
868
+ """
869
+ Generate queries to create indexes for the given CartographyRelSchema and all node types attached to it via its
870
+ relationships.
871
+ :param rel_schema: The CartographyRelSchema object
872
+ :return: A list of queries of the form `CREATE INDEX IF NOT EXISTS FOR (n:$TargetNodeLabel) ON (n.$TargetAttribute)`
873
+ """
874
+ if not rel_schema.source_node_matcher:
875
+ logger.warning(
876
+ f"No source node matcher found for {rel_schema.rel_label}; returning empty list."
877
+ "Please note that build_create_index_queries_for_matchlink() is only used for load_matchlinks() where we match on "
878
+ "and connect existing nodes in the graph."
879
+ )
880
+ return []
881
+
882
+ index_template = Template(
883
+ "CREATE INDEX IF NOT EXISTS FOR (n:$NodeLabel) ON (n.$NodeAttribute);",
884
+ )
885
+
886
+ result = []
887
+ for source_key in asdict(rel_schema.source_node_matcher).keys():
888
+ result.append(
889
+ index_template.safe_substitute(
890
+ NodeLabel=rel_schema.source_node_label,
891
+ NodeAttribute=source_key,
892
+ ),
893
+ )
894
+ for target_key in asdict(rel_schema.target_node_matcher).keys():
895
+ result.append(
896
+ index_template.safe_substitute(
897
+ NodeLabel=rel_schema.target_node_label,
898
+ NodeAttribute=target_key,
899
+ ),
900
+ )
901
+
902
+ # Create a composite index for the relationship between the source and target nodes.
903
+ # https://neo4j.com/docs/cypher-manual/4.3/indexes-for-search-performance/#administration-indexes-create-a-composite-index-for-relationships
904
+ rel_index_template = Template(
905
+ "CREATE INDEX IF NOT EXISTS FOR ()$rel_direction[r:$RelLabel]$rel_direction_end() "
906
+ "ON (r.lastupdated, r._sub_resource_label, r._sub_resource_id);",
907
+ )
908
+ if rel_schema.direction == LinkDirection.INWARD:
909
+ result.append(
910
+ rel_index_template.safe_substitute(
911
+ RelLabel=rel_schema.rel_label,
912
+ rel_direction="<-",
913
+ rel_direction_end="-",
914
+ )
915
+ )
916
+ else:
917
+ result.append(
918
+ rel_index_template.safe_substitute(
919
+ RelLabel=rel_schema.rel_label,
920
+ rel_direction="-",
921
+ rel_direction_end="->",
922
+ )
923
+ )
924
+ return result
925
+
926
+
927
+ def build_matchlink_query(rel_schema: CartographyRelSchema) -> str:
928
+ """
929
+ Generate a Neo4j query to link two existing nodes when given a CartographyRelSchema object.
930
+ This is only used for load_matchlinks().
931
+ :param rel_schema: The CartographyRelSchema object to generate a query. This CartographyRelSchema object
932
+ - Must have a source_node_matcher and source_node_label defined
933
+ - Must have a CartographyRelProperties object where _sub_resource_label and _sub_resource_id are defined
934
+ :return: A Neo4j query that can be used to link two existing nodes.
935
+ """
936
+ if not rel_schema.source_node_matcher or not rel_schema.source_node_label:
937
+ raise ValueError(
938
+ f"No source node matcher or source node label found for {rel_schema.rel_label}. "
939
+ "MatchLink relationships require a source_node_matcher and source_node_label to be defined."
940
+ )
941
+
942
+ rel_props_as_dict = _asdict_with_validate_relprops(rel_schema)
943
+
944
+ # These are needed for the cleanup query
945
+ if "_sub_resource_label" not in rel_props_as_dict:
946
+ raise ValueError(
947
+ f"Expected _sub_resource_label to be defined on {rel_schema.properties.__class__.__name__}"
948
+ "Please include `_sub_resource_label: PropertyRef = PropertyRef('_sub_resource_label', set_in_kwargs=True)`"
949
+ )
950
+ if "_sub_resource_id" not in rel_props_as_dict:
951
+ raise ValueError(
952
+ f"Expected _sub_resource_id to be defined on {rel_schema.properties.__class__.__name__}"
953
+ "Please include `_sub_resource_id: PropertyRef = PropertyRef('_sub_resource_id', set_in_kwargs=True)`"
954
+ )
955
+
956
+ matchlink_query_template = Template(
957
+ """
958
+ UNWIND $DictList as item
959
+ $source_match
960
+ $target_match
961
+ MERGE $rel
962
+ ON CREATE SET r.firstseen = timestamp()
963
+ SET
964
+ r._module_name = "$module_name",
965
+ r._module_version = "$module_version",
966
+ $set_rel_properties_statement;
967
+ """
968
+ )
969
+
970
+ source_match = Template(
971
+ "MATCH (from:$source_node_label{$match_clause})"
972
+ ).safe_substitute(
973
+ source_node_label=rel_schema.source_node_label,
974
+ match_clause=_build_match_clause(rel_schema.source_node_matcher),
975
+ )
976
+
977
+ target_match = Template(
978
+ "MATCH (to:$target_node_label{$match_clause})"
979
+ ).safe_substitute(
980
+ target_node_label=rel_schema.target_node_label,
981
+ match_clause=_build_match_clause(rel_schema.target_node_matcher),
982
+ )
983
+
984
+ if rel_schema.direction == LinkDirection.INWARD:
985
+ rel = f"(from)<-[r:{rel_schema.rel_label}]-(to)"
986
+ else:
987
+ rel = f"(from)-[r:{rel_schema.rel_label}]->(to)"
988
+
989
+ return matchlink_query_template.safe_substitute(
990
+ source_match=source_match,
991
+ target_match=target_match,
992
+ rel=rel,
993
+ module_name=_get_module_from_schema(rel_schema),
994
+ module_version=_get_cartography_version(),
995
+ set_rel_properties_statement=_build_rel_properties_statement(
996
+ "r",
997
+ rel_props_as_dict,
998
+ ),
999
+ )
1000
+
1001
+
1002
+ def _get_cartography_version() -> str:
1003
+ """
1004
+ Get the current version of the cartography package.
1005
+
1006
+ This function attempts to retrieve the version of the installed cartography package
1007
+ using importlib.metadata. If the package is not found (typically in development
1008
+ or testing environments), it returns 'dev' as a fallback.
1009
+
1010
+ Returns:
1011
+ The version string of the cartography package, or 'dev' if not found
1012
+ """
1013
+ try:
1014
+ return version("cartography")
1015
+ except PackageNotFoundError:
1016
+ # This can occured if the cartography package is not installed in the environment, typically in development or testing environments.
1017
+ logger.warning("cartography package not found. Returning 'dev' version.")
1018
+ # Fallback to reading the VERSION file if the package is not found
1019
+ return "dev"
1020
+
1021
+
1022
+ def _get_module_from_schema(
1023
+ schema, #: "CartographyNodeSchema" | "CartographyRelSchema",
1024
+ ) -> str:
1025
+ """
1026
+ Extract the module name from a Cartography schema object.
1027
+
1028
+ This function extracts and formats the module name from a CartographyNodeSchema
1029
+ or CartographyRelSchema object. It expects schemas to be part of the official
1030
+ cartography.models package hierarchy and returns a formatted string indicating
1031
+ the specific cartography module.
1032
+
1033
+ Args:
1034
+ schema: A CartographyNodeSchema or CartographyRelSchema object
1035
+
1036
+ Returns:
1037
+ A formatted module name string in the format 'cartography:<module_name>'
1038
+ or 'unknown:<full_module_path>' if the schema is not from cartography.models
1039
+ """
1040
+ # If the entity schema does not belong to the cartography.models package,
1041
+ # we log a warning and return the full module path.
1042
+ if not schema.__module__.startswith("cartography.models."):
1043
+ logger.warning(
1044
+ "The schema %s does not start with 'cartography.models.'. "
1045
+ "This may indicate that the schema is not part of the official cartography models.",
1046
+ schema.__module__,
1047
+ )
1048
+ return f"unknown:{schema.__module__}"
1049
+ # Otherwise, we return the module path as a string.
1050
+ parts = schema.__module__.split(".")
1051
+ return f"cartography:{parts[2]}"