cartography 0.92.0rc2__py3-none-any.whl → 0.93.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.

@@ -51,6 +51,7 @@ def get_images(boto3_session: boto3.session.Session, region: str, image_ids: Lis
51
51
  logger.warning(f"Failed retrieve images for region - {region}. Error - {e}")
52
52
  try:
53
53
  if image_ids:
54
+ image_ids = [image_id for image_id in image_ids if image_id is not None]
54
55
  images_in_use = client.describe_images(ImageIds=image_ids)['Images']
55
56
  # Ensure we're not adding duplicates
56
57
  _ids = [image["ImageId"] for image in images]
@@ -3,9 +3,11 @@ from typing import Any
3
3
 
4
4
  import boto3
5
5
  import neo4j
6
+ from botocore.exceptions import ClientError
6
7
 
7
8
  from .util import get_botocore_config
8
9
  from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
9
11
  from cartography.models.aws.ec2.launch_template_versions import LaunchTemplateVersionSchema
10
12
  from cartography.models.aws.ec2.launch_templates import LaunchTemplateSchema
11
13
  from cartography.util import aws_handle_regions
@@ -16,13 +18,30 @@ logger = logging.getLogger(__name__)
16
18
 
17
19
  @timeit
18
20
  @aws_handle_regions
19
- def get_launch_templates(boto3_session: boto3.session.Session, region: str) -> list[dict[str, Any]]:
21
+ def get_launch_templates(
22
+ boto3_session: boto3.session.Session,
23
+ region: str,
24
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
20
25
  client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
21
26
  paginator = client.get_paginator('describe_launch_templates')
22
27
  templates: list[dict[str, Any]] = []
28
+ template_versions: list[dict[str, Any]] = []
23
29
  for page in paginator.paginate():
24
- templates.extend(page['LaunchTemplates'])
25
- return templates
30
+ paginated_templates = page['LaunchTemplates']
31
+ for template in paginated_templates:
32
+ template_id = template['LaunchTemplateId']
33
+ try:
34
+ versions = get_launch_template_versions_by_template(boto3_session, template_id, region)
35
+ except ClientError as e:
36
+ logger.warning(
37
+ f"Failed to get launch template versions for {template_id}: {e}",
38
+ exc_info=True,
39
+ )
40
+ versions = []
41
+ # Using a key not defined in latest boto3 documentation
42
+ template_versions.extend(versions)
43
+ templates.extend(paginated_templates)
44
+ return templates, template_versions
26
45
 
27
46
 
28
47
  def transform_launch_templates(templates: list[dict[str, Any]]) -> list[dict[str, Any]]:
@@ -54,17 +73,16 @@ def load_launch_templates(
54
73
 
55
74
  @timeit
56
75
  @aws_handle_regions
57
- def get_launch_template_versions(
76
+ def get_launch_template_versions_by_template(
58
77
  boto3_session: boto3.session.Session,
59
- templates: list[dict[str, Any]],
78
+ template: str,
60
79
  region: str,
61
80
  ) -> list[dict[str, Any]]:
62
81
  client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
63
82
  v_paginator = client.get_paginator('describe_launch_template_versions')
64
83
  template_versions = []
65
- for template in templates:
66
- for versions in v_paginator.paginate(LaunchTemplateId=template['LaunchTemplateId']):
67
- template_versions.extend(versions['LaunchTemplateVersions'])
84
+ for versions in v_paginator.paginate(LaunchTemplateId=template):
85
+ template_versions.extend(versions['LaunchTemplateVersions'])
68
86
  return template_versions
69
87
 
70
88
 
@@ -114,6 +132,16 @@ def load_launch_template_versions(
114
132
  )
115
133
 
116
134
 
135
+ @timeit
136
+ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]) -> None:
137
+ logger.info("Running launch template cleanup job.")
138
+ cleanup_job = GraphJob.from_node_schema(LaunchTemplateSchema(), common_job_parameters)
139
+ cleanup_job.run(neo4j_session)
140
+
141
+ cleanup_job = GraphJob.from_node_schema(LaunchTemplateVersionSchema(), common_job_parameters)
142
+ cleanup_job.run(neo4j_session)
143
+
144
+
117
145
  @timeit
118
146
  def sync_ec2_launch_templates(
119
147
  neo4j_session: neo4j.Session,
@@ -125,10 +153,10 @@ def sync_ec2_launch_templates(
125
153
  ) -> None:
126
154
  for region in regions:
127
155
  logger.info(f"Syncing launch templates for region '{region}' in account '{current_aws_account_id}'.")
128
- templates = get_launch_templates(boto3_session, region)
156
+ templates, versions = get_launch_templates(boto3_session, region)
129
157
  templates = transform_launch_templates(templates)
130
158
  load_launch_templates(neo4j_session, templates, region, current_aws_account_id, update_tag)
131
-
132
- versions = get_launch_template_versions(boto3_session, templates, region)
133
159
  versions = transform_launch_template_versions(versions)
134
160
  load_launch_template_versions(neo4j_session, versions, region, current_aws_account_id, update_tag)
161
+
162
+ cleanup(neo4j_session, common_job_parameters)
@@ -227,6 +227,7 @@ def get_account_access_key_data(boto3_session: boto3.session.Session, username:
227
227
  logger.warning(
228
228
  f"Could not get access key for user {username} due to NoSuchEntityException; skipping.",
229
229
  )
230
+ return access_keys
230
231
  for access_key in access_keys['AccessKeyMetadata']:
231
232
  access_key_id = access_key['AccessKeyId']
232
233
  last_used_info = client.get_access_key_last_used(
@@ -43,7 +43,7 @@ def load_vms(neo4j_session: neo4j.Session, subscription_id: str, vm_list: List[D
43
43
  v.resourcegroup = vm.resource_group
44
44
  SET v.lastupdated = $update_tag, v.name = vm.name,
45
45
  v.plan = vm.plan.product, v.size = vm.hardware_profile.vm_size,
46
- v.license_type=vm.license_type, v.computer_name=vm.os_profile.computer_ame,
46
+ v.license_type=vm.license_type, v.computer_name=vm.os_profile.computer_name,
47
47
  v.identity_type=vm.identity.type, v.zones=vm.zones,
48
48
  v.ultra_ssd_enabled=vm.additional_capabilities.ultra_ssd_enabled,
49
49
  v.priority=vm.priority, v.eviction_policy=vm.eviction_policy
@@ -76,9 +76,9 @@ def load_host_data(
76
76
  )
77
77
 
78
78
 
79
- def get_host_ids(client: Hosts) -> List[List[str]]:
79
+ def get_host_ids(client: Hosts, crowdstrikeapi_filter: str = '', crowdstrikeapi_limit: int = 5000) -> List[List[str]]:
80
80
  ids = []
81
- parameters = {"filter": 'service_provider:"AWS_EC2"', "limit": 400}
81
+ parameters = {"filter": crowdstrikeapi_filter, "limit": crowdstrikeapi_limit}
82
82
  response = client.QueryDevicesByFilter(parameters=parameters)
83
83
  body = response.get("body", {})
84
84
  resources = body.get("resources", [])
@@ -23,7 +23,8 @@ from cartography.util import timeit
23
23
  logger = logging.getLogger(__name__)
24
24
 
25
25
  MAX_RETRIES = 3
26
- REQUEST_TIMEOUT = 10
26
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
27
+ CONNECT_AND_READ_TIMEOUT = (60, 60)
27
28
  CVE_FEED_ID = "NIST_NVD"
28
29
  BATCH_SIZE_DAYS = 120
29
30
  RESULTS_PER_PAGE = 2000
@@ -87,7 +88,7 @@ def _call_cves_api(url: str, api_key: str, params: Dict[str, Any]) -> Dict[Any,
87
88
  while params["resultsPerPage"] > 0 or params["startIndex"] < totalResults:
88
89
  try:
89
90
  res = requests.get(
90
- url, params=params, headers=headers, timeout=REQUEST_TIMEOUT,
91
+ url, params=params, headers=headers, timeout=CONNECT_AND_READ_TIMEOUT,
91
92
  )
92
93
  res.raise_for_status()
93
94
  except requests.exceptions.HTTPError:
@@ -15,7 +15,7 @@ from cartography.models.core.relationships import TargetNodeMatcher
15
15
  class EC2SubnetInstanceNodeProperties(CartographyNodeProperties):
16
16
  # arn: PropertyRef = PropertyRef('Arn', extra_index=True) TODO use arn; issue #1024
17
17
  id: PropertyRef = PropertyRef('SubnetId')
18
- subnet_id: PropertyRef = PropertyRef('SubnetId', extra_index=True)
18
+ subnetid: PropertyRef = PropertyRef('SubnetId', extra_index=True)
19
19
  region: PropertyRef = PropertyRef('Region', set_in_kwargs=True)
20
20
  lastupdated: PropertyRef = PropertyRef('lastupdated', set_in_kwargs=True)
21
21
 
cartography/util.py CHANGED
@@ -225,7 +225,7 @@ If not, then the AWS datatype somehow does not have this key.''',
225
225
  return items
226
226
 
227
227
 
228
- AWSGetFunc = TypeVar('AWSGetFunc', bound=Callable[..., List])
228
+ AWSGetFunc = TypeVar('AWSGetFunc', bound=Callable[..., Iterable])
229
229
 
230
230
  # fix for AWS TooManyRequestsException
231
231
  # https://github.com/lyft/cartography/issues/297
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cartography
3
- Version: 0.92.0rc2
3
+ Version: 0.93.0
4
4
  Summary: Explore assets and their relationships across your technical infrastructure.
5
5
  Home-page: https://www.github.com/lyft/cartography
6
6
  Maintainer: Lyft
@@ -5,7 +5,7 @@ cartography/config.py,sha256=rL1zgxZO47_R7S6E9e0CwxmhzRSN0X_q93NtcPR1G00,11368
5
5
  cartography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  cartography/stats.py,sha256=dbybb9V2FuvSuHjjNwz6Vjwnd1hap2C7h960rLoKcl8,4406
7
7
  cartography/sync.py,sha256=a80r_IzrZcWGSmRDRrxkesNYPiOuLte5YHvDQT3L-Lw,9730
8
- cartography/util.py,sha256=F3FPMJl1KDW0x_5cvt2ZGI0Dv1LVrHU7Az4OleAANBI,14474
8
+ cartography/util.py,sha256=umfnjX8jVLu0rpYA75X-WvRpYzHQxns9qZiPwfyAlwQ,14478
9
9
  cartography/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  cartography/client/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cartography/client/aws/iam.py,sha256=dYsGikc36DEsSeR2XVOVFFUDwuU9yWj_EVkpgVYCFgM,1293
@@ -152,7 +152,7 @@ cartography/intel/aws/eks.py,sha256=OerAX7qT2uGPbqliPvuy8JZUIgle_KMlnkkHxk8O5fk,
152
152
  cartography/intel/aws/elasticache.py,sha256=fCI47aDFmIDyE26GiReKYb6XIZUwrzcvsXBQ4ruFhuI,4427
153
153
  cartography/intel/aws/elasticsearch.py,sha256=ZL7MkXF_bXRSoXuDSI1dwGckRLG2zDB8LuAD07vSLnE,8374
154
154
  cartography/intel/aws/emr.py,sha256=xhWBVZngxJRFjMEDxwq3G6SgytRGLq0v2a_CeDvByR0,3372
155
- cartography/intel/aws/iam.py,sha256=fEn0XCQhBKxoEQJT5OCEzTbP58VXtfzF11GUK-Fnqog,32347
155
+ cartography/intel/aws/iam.py,sha256=eLw0NkBGKzCI_tQ3wmrx3aUibQerrsxKJd3d0RCKcKQ,32374
156
156
  cartography/intel/aws/inspector.py,sha256=6enCu2USefuGT3FqA0Vto6i-z4BrL2HC_clbiXSLIlo,8654
157
157
  cartography/intel/aws/kms.py,sha256=bZUzMxAH_DsAcGTJBs08gg2tLKYu-QWjvMvV9C-6v50,11731
158
158
  cartography/intel/aws/lambda_function.py,sha256=KKTyn53xpaMI9WvIqxmsOASFwflHt-2_5ow-zUFc2wg,9890
@@ -171,11 +171,11 @@ cartography/intel/aws/ssm.py,sha256=IDOYa8v2FgziU8nBOZ7wyDG4o_nFYshbB-si9Ut_9Zc,
171
171
  cartography/intel/aws/ec2/__init__.py,sha256=IDK2Yap7mllK_ab6yVMLXatJ94znIkn-szv5RJP5fbo,346
172
172
  cartography/intel/aws/ec2/auto_scaling_groups.py,sha256=4erjP31KSVW-Pp2ASmDox_VLp_AQUAin4KYxfZKZcSM,9223
173
173
  cartography/intel/aws/ec2/elastic_ip_addresses.py,sha256=0k4NwS73VyWbEj5jXvSkaq2RNvmAlBlrN-UKa_Bj0uk,3957
174
- cartography/intel/aws/ec2/images.py,sha256=DcGHJwZf8_K5iRCDJ2QOdP837TTJTwST02IRswfk9Mc,3697
174
+ cartography/intel/aws/ec2/images.py,sha256=heElwHJGqVD3iUJjxwA_Sdc3CmE4HPs00CTMHuQ1wkc,3782
175
175
  cartography/intel/aws/ec2/instances.py,sha256=mnTjdBY-4D-TGhH29UrSaLUW0Uft0JApDIJkkLz4zPc,12170
176
176
  cartography/intel/aws/ec2/internet_gateways.py,sha256=dI-4-85_3DGGZZBcY_DN6XqESx9P26S6jKok314lcnQ,2883
177
177
  cartography/intel/aws/ec2/key_pairs.py,sha256=SvRgd56vE4eouvTSNoFK8PP8HYoECO91goxc36oq_FY,2508
178
- cartography/intel/aws/ec2/launch_templates.py,sha256=GSOtwvqL6pG2fcROhhjXjOPntW-nR8msT-dQG_JuQkM,4862
178
+ cartography/intel/aws/ec2/launch_templates.py,sha256=aeqaL8On38ET8nM8bISsIXLy6PkZoV-tqSWG38YXgkI,6010
179
179
  cartography/intel/aws/ec2/load_balancer_v2s.py,sha256=95FfQQn740gexINIHDJizOM4OKzRtQT_y2XQMipQ5Dg,8661
180
180
  cartography/intel/aws/ec2/load_balancers.py,sha256=1GwErzGqi3BKCARqfGJcD_r_D84rFKVy5kNMas9jAok,6756
181
181
  cartography/intel/aws/ec2/network_interfaces.py,sha256=CzF8PooCYUQ2pk8DR8JDAhkWRUQSBj_27OsIfkL_-Cs,9199
@@ -192,7 +192,7 @@ cartography/intel/aws/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
192
192
  cartography/intel/aws/util/arns.py,sha256=TEjrQEDbJM8Zwy6Eizh-vAFd9kMr5AKsdOsHYQHUSbk,573
193
193
  cartography/intel/aws/util/common.py,sha256=j_K9_Iy42dgCSvbGLdtJW8bRs_qTiJu4wkz54OId7w8,886
194
194
  cartography/intel/azure/__init__.py,sha256=RxbDGfsfDBM1eGtS-FPULB5mBBl3yYl7fIZ5P3RD9RE,3696
195
- cartography/intel/azure/compute.py,sha256=WbdsYI-mKgNENrWRtV_lwEKPLQsbw-Pk1yDLTHyNLNk,8678
195
+ cartography/intel/azure/compute.py,sha256=SIjvhmGp2tSpBpzNubIM1pgr_MrVHHuJsJxGpnFWDRw,8679
196
196
  cartography/intel/azure/cosmosdb.py,sha256=8ZtQntjkMUVsQUcjGgJw6geDjYqothPkssyNQALhJ0o,41275
197
197
  cartography/intel/azure/sql.py,sha256=Z-C-7BRjD5y-enNKg0sfWgv1-GIENCf2y0Z3bZiasdc,32865
198
198
  cartography/intel/azure/storage.py,sha256=56QyHafWTNproCOjITyAaWsmsqKhyFwJQlVEfwawBNg,27393
@@ -203,13 +203,13 @@ cartography/intel/azure/util/credentials.py,sha256=99PjTs0vZ2iu0tHD7TohN1VJYjuXY
203
203
  cartography/intel/bigfix/__init__.py,sha256=3LoDCm01VNNaDRGsRiykJm1GJgUQ8zI1HO6NodLFVIA,1058
204
204
  cartography/intel/bigfix/computers.py,sha256=HAwA-muDBLu2xkFFL2Ae-xjCH1gxKKwxGMZM4BE_Qdo,5909
205
205
  cartography/intel/crowdstrike/__init__.py,sha256=dAtgI-0vZAQZ3cTFQhMEzzt7aqiNSNuiIYiw0qbut-I,1896
206
- cartography/intel/crowdstrike/endpoints.py,sha256=o45309ZDNdlYdG0YicIiBain-nkr3bQahBaRx3N9Mow,3738
206
+ cartography/intel/crowdstrike/endpoints.py,sha256=tdqokMDW3p4fK3dHKKb2T1DTogvOJBCpwyrxdQlbUhw,3815
207
207
  cartography/intel/crowdstrike/spotlight.py,sha256=yNhj44-RYF6ubck-hHMKhKiNU0fCfhQf4Oagopc31EM,4754
208
208
  cartography/intel/crowdstrike/util.py,sha256=gfJ6Ptr6YdbBS9Qj9a_-Jc-IJroADDRcXqjh5TW0qXE,277
209
209
  cartography/intel/crxcavator/__init__.py,sha256=VM6N_7dMagzuQQjUeFgqrt2_d2Is9ugDMTrgKke2c0g,1606
210
210
  cartography/intel/crxcavator/crxcavator.py,sha256=tnx6bq8Oz020mhMDmx8gKZ_ro_0UvUGeWrshmFr7bBw,13797
211
211
  cartography/intel/cve/__init__.py,sha256=A7XjKQSanmwMSIXSum1qJSegtYcQCuz_713RU-bFQz8,2504
212
- cartography/intel/cve/feed.py,sha256=i3EuRFhotu93MNMkhwBQp-zvK6itLgdAdRFOlZy5iLc,9878
212
+ cartography/intel/cve/feed.py,sha256=JkfRV18JoydOuncKR1y3s8esuN2Xk4gIB6viKNXU_X0,10020
213
213
  cartography/intel/digitalocean/__init__.py,sha256=SMYB7LGIQOj_EgGSGVjWZk7SJNbP43hQuOfgOu6xYm4,1526
214
214
  cartography/intel/digitalocean/compute.py,sha256=9XctwMjq9h5dExFgExvawoqyiEwSoocNgaMm3Fgl5GM,4911
215
215
  cartography/intel/digitalocean/management.py,sha256=YWRnBLLL_bAP1vefIAQgm_-QzefGH0sZKmyU_EokHfA,3764
@@ -291,7 +291,7 @@ cartography/models/aws/ec2/privateip_networkinterface.py,sha256=j8MyiZsiUCuzuGUH
291
291
  cartography/models/aws/ec2/reservations.py,sha256=dE9uSB3Em-ca1dDbetbu79JXr4ZWHC3r5gA1S3mjhVU,1930
292
292
  cartography/models/aws/ec2/securitygroup_instance.py,sha256=RZS9TzHHatTOESQgcs5_YHmF9sM7pRkUq2VPjk9FJlU,2876
293
293
  cartography/models/aws/ec2/securitygroup_networkinterface.py,sha256=PiaA8J82kybZyZ1wDsa-ACIDa88vt4NoA3smGNiwl14,2399
294
- cartography/models/aws/ec2/subnet_instance.py,sha256=kPELT07l6AXIy-NYnXfkhM_RWUG20D33K1V1CJQdHyw,2753
294
+ cartography/models/aws/ec2/subnet_instance.py,sha256=ct_ibXiPN2C5ld06TczwSTXtsnlov5VCW6elphtbvPs,2752
295
295
  cartography/models/aws/ec2/subnet_networkinterface.py,sha256=JHlxfBojBw7LfJS4a5LpVGM28MUu451PUrrwbbOPGuQ,3614
296
296
  cartography/models/aws/ec2/volumes.py,sha256=WSP7YNZeJE3s4wnY9QrIAbcJN3OathqNgEBX0cVahDg,4470
297
297
  cartography/models/aws/eks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -332,10 +332,10 @@ cartography/models/semgrep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
332
332
  cartography/models/semgrep/deployment.py,sha256=or5qZDuR51MXzINpH15jZrqmSUvXQevCNYWJ7D6v-JI,745
333
333
  cartography/models/semgrep/findings.py,sha256=xrn8sgXpNMrNJbKQagaAVxaCG9bVjTATSRR2XRBR4rg,5386
334
334
  cartography/models/semgrep/locations.py,sha256=kSk7Nn5Mn4Ob84MVZOo2GR0YFi-9Okq9pgA3FfC6_bk,3061
335
- cartography-0.92.0rc2.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
336
- cartography-0.92.0rc2.dist-info/METADATA,sha256=67pexbhOi_WeS7TLuIGswFYPCfMbzOz6eSibk8FgvUo,1991
337
- cartography-0.92.0rc2.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
338
- cartography-0.92.0rc2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
339
- cartography-0.92.0rc2.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
340
- cartography-0.92.0rc2.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
341
- cartography-0.92.0rc2.dist-info/RECORD,,
335
+ cartography-0.93.0.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
336
+ cartography-0.93.0.dist-info/METADATA,sha256=bZcpA3W4BYf5mQXLdALJ0KEf3_n_PqMI4TiPSda3Bv8,1988
337
+ cartography-0.93.0.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
338
+ cartography-0.93.0.dist-info/WHEEL,sha256=pd56usn78UTvq1xeX_ZwFhoK6jE5u5wzu4TTBIG5cQ0,91
339
+ cartography-0.93.0.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
340
+ cartography-0.93.0.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
341
+ cartography-0.93.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (71.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5