primitive 0.2.59__py3-none-any.whl → 0.2.61__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.
primitive/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Dylan Stein <dylan@primitive.tech>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.2.59"
4
+ __version__ = "0.2.61"
@@ -124,15 +124,39 @@ class Files(BaseAction):
124
124
  return result
125
125
 
126
126
  @guard
127
- def get_file(self, file_id: Optional[str] = None):
127
+ def files(
128
+ self,
129
+ file_id: Optional[str] = None,
130
+ file_name: Optional[str] = None,
131
+ organization_id: Optional[str] = None,
132
+ organization_slug: Optional[str] = None,
133
+ ):
128
134
  query = gql(files_list)
129
135
 
130
136
  filters = {}
137
+ if not organization_id and not organization_slug:
138
+ whoami_result = self.primitive.auth.whoami()
139
+ default_organization = whoami_result.data["whoami"]["defaultOrganization"]
140
+ organization_id = default_organization["id"]
141
+ logger.info(
142
+ f"Using default organization ID: {default_organization.get('slug')} ({organization_id})"
143
+ )
144
+ if organization_slug and not organization_id:
145
+ organization = self.primitive.organizations.get_organization(
146
+ slug=organization_slug
147
+ )
148
+ organization_id = organization.get("id")
149
+
150
+ if organization_id:
151
+ filters["organization"] = {"id": organization_id}
152
+
131
153
  if file_id:
132
- filters["id"] = {"exact": file_id}
154
+ filters["id"] = file_id
155
+ if file_name:
156
+ filters["fileName"] = {"exact": file_name}
133
157
 
134
158
  variables = {
135
- "first": 1,
159
+ "first": 25,
136
160
  "filters": filters,
137
161
  }
138
162
  result = self.primitive.session.execute(
@@ -242,9 +266,9 @@ class Files(BaseAction):
242
266
  file_id = pending_file_create.get("id")
243
267
  parts_details = pending_file_create.get("partsDetails")
244
268
  else:
245
- get_file_result = self.get_file(file_id)
269
+ file_result = self.files(file_id=file_id)
246
270
  parts_details = (
247
- get_file_result.data.get("files")
271
+ file_result.data.get("files")
248
272
  .get("edges")[0]
249
273
  .get("node")
250
274
  .get("partsDetails")
@@ -340,14 +364,56 @@ class Files(BaseAction):
340
364
  "fileObject": (path.name, open(path, "rb")),
341
365
  }
342
366
 
343
- session = create_requests_session(self.primitive.host_config)
367
+ session = create_requests_session(host_config=self.primitive.host_config)
344
368
  transport = self.primitive.host_config.get("transport")
345
369
  url = f"{transport}://{self.primitive.host}/"
346
370
  response = session.post(url, files=body)
347
371
  return response
348
372
 
349
- def get_presigned_url(self, file_pk: str):
373
+ def get_presigned_url(self, file_pk: str) -> str:
350
374
  transport = self.primitive.host_config.get("transport")
351
375
  host = self.primitive.host_config.get("host")
352
376
  file_access_url = f"{transport}://{host}/files/{file_pk}/presigned-url/"
353
377
  return file_access_url
378
+
379
+ def download_file(
380
+ self,
381
+ file_name: str = "",
382
+ file_id: str = "",
383
+ organization_id: str = "",
384
+ organization_slug: str = "",
385
+ output_path: Path = Path().cwd(),
386
+ ) -> Path:
387
+ file_pk = None
388
+
389
+ files_result = self.primitive.files.files(
390
+ file_id=file_id,
391
+ file_name=file_name,
392
+ organization_id=organization_id,
393
+ organization_slug=organization_slug,
394
+ )
395
+ if files_data := files_result.data:
396
+ file = files_data["files"]["edges"][0]["node"]
397
+ file_pk = file["pk"]
398
+ file_name = file["fileName"]
399
+
400
+ if not file_pk:
401
+ raise Exception(
402
+ "File not found on remote server. Please check file name or file id"
403
+ )
404
+
405
+ session = create_requests_session(host_config=self.primitive.host_config)
406
+ transport = self.primitive.host_config.get("transport")
407
+ url = f"{transport}://{self.primitive.host}/files/{file_pk}/stream/"
408
+
409
+ downloaded_file = output_path / file_name
410
+
411
+ with session.get(url, stream=True) as response:
412
+ response.raise_for_status()
413
+ with open(downloaded_file, "wb") as partial_downloaded_file:
414
+ for chunk in response.iter_content(chunk_size=8192):
415
+ if chunk:
416
+ partial_downloaded_file.write(chunk)
417
+ partial_downloaded_file.flush()
418
+
419
+ return downloaded_file
@@ -4,6 +4,8 @@ from pathlib import Path
4
4
 
5
5
  import click
6
6
 
7
+ from primitive.files.ui import render_files_table
8
+
7
9
  from ..utils.printer import print_result
8
10
 
9
11
  if typing.TYPE_CHECKING:
@@ -22,7 +24,7 @@ def cli(context):
22
24
  @click.argument("path", type=click.Path(exists=True))
23
25
  @click.option("--public", "-p", help="Is this a Public file", is_flag=True)
24
26
  @click.option("--key-prefix", "-k", help="Key Prefix", default="")
25
- @click.option("--direct", "-k", help="direct", is_flag=True)
27
+ @click.option("--direct", help="direct", is_flag=True)
26
28
  def file_upload_command(context, path, public, key_prefix, direct):
27
29
  """File Upload"""
28
30
  primitive: Primitive = context.obj.get("PRIMITIVE")
@@ -36,8 +38,63 @@ def file_upload_command(context, path, public, key_prefix, direct):
36
38
  path, is_public=public, key_prefix=key_prefix
37
39
  )
38
40
  try:
39
- message = json.dumps(result.data)
41
+ message = json.dumps(result.json())
40
42
  except AttributeError:
41
43
  message = "File Upload Failed"
42
44
 
43
45
  print_result(message=message, context=context)
46
+
47
+
48
+ @cli.command("download")
49
+ @click.pass_context
50
+ @click.option("--file-id", help="File ID", required=False)
51
+ @click.option("--file-name", help="File Name", required=False)
52
+ @click.option("--output", help="Output Path", required=False, type=click.Path())
53
+ @click.option("--organization-id", help="Organization ID", required=False)
54
+ @click.option("--organization", help="Organization Slug", required=False)
55
+ def file_download_command(
56
+ context,
57
+ file_id=None,
58
+ file_name=None,
59
+ output=None,
60
+ organization_id=None,
61
+ organization=None,
62
+ ):
63
+ """File Download"""
64
+ primitive: Primitive = context.obj.get("PRIMITIVE")
65
+
66
+ if not file_id and not file_name:
67
+ raise click.UsageError("Either --id or --file-name must be provided.")
68
+
69
+ if not output:
70
+ output = Path().cwd()
71
+ else:
72
+ output = Path(output)
73
+
74
+ downloaded_file = primitive.files.download_file(
75
+ output_path=output,
76
+ file_id=file_id,
77
+ file_name=file_name,
78
+ organization_id=organization_id,
79
+ organization_slug=organization,
80
+ )
81
+ print_result(message=f"File downloaded to {downloaded_file}", context=context)
82
+
83
+
84
+ @cli.command("list")
85
+ @click.pass_context
86
+ @click.option("--organization-id", help="Organization ID", required=False)
87
+ @click.option("--organization", help="Organization Slug", required=False)
88
+ def list_command(context, organization_id=None, organization=None):
89
+ """List Files"""
90
+ primitive: Primitive = context.obj.get("PRIMITIVE")
91
+ files_result = primitive.files.files(
92
+ organization_id=organization_id, organization_slug=organization
93
+ )
94
+
95
+ files = [file.get("node") for file in files_result.data.get("files").get("edges")]
96
+
97
+ if context.obj["JSON"]:
98
+ print_result(message=files, context=context)
99
+ else:
100
+ render_files_table(files)
@@ -4,7 +4,11 @@ fragment FileFragment on File {
4
4
  pk
5
5
  createdAt
6
6
  updatedAt
7
- createdBy
7
+ createdBy {
8
+ id
9
+ pk
10
+ username
11
+ }
8
12
  location
9
13
  fileName
10
14
  fileSize
@@ -13,6 +17,5 @@ fragment FileFragment on File {
13
17
  isComplete
14
18
  partsDetails
15
19
  humanReadableMemorySize
16
- contents
17
20
  }
18
21
  """
primitive/files/ui.py ADDED
@@ -0,0 +1,37 @@
1
+ from rich.console import Console
2
+ from rich.table import Table
3
+
4
+
5
+ def render_files_table(file_list) -> None:
6
+ console = Console()
7
+
8
+ table = Table(show_header=True, header_style="bold #FFA800")
9
+ table.add_column("File Name")
10
+ table.add_column("File ID")
11
+ table.add_column("File Size (bytes)", justify="right")
12
+
13
+ for file in file_list:
14
+ file_name = file.get("fileName")
15
+ file_id = file.get("id")
16
+ file_size = file.get("fileSize")
17
+
18
+ table.add_row(
19
+ file_name,
20
+ file_id,
21
+ file_size,
22
+ )
23
+
24
+ console.print(table)
25
+
26
+
27
+ def file_status_string(file) -> str:
28
+ if file.get("isQuarantined"):
29
+ return "Quarantined"
30
+ if not file.get("isOnline"):
31
+ return "Offline"
32
+ if not file.get("isHealthy"):
33
+ return "Not healthy"
34
+ if not file.get("isAvailable"):
35
+ return "Not available"
36
+ else:
37
+ return "Available"
@@ -740,10 +740,11 @@ class Hardware(BaseAction):
740
740
  logger.exception(f"Error checking in children: {exception}")
741
741
 
742
742
  def push_metrics(self):
743
- self.primitive.messaging.send_message(
744
- message_type=MESSAGE_TYPES.METRICS,
745
- message=self.get_metrics(),
746
- )
743
+ if self.primitive.messaging.ready:
744
+ self.primitive.messaging.send_message(
745
+ message_type=MESSAGE_TYPES.METRICS,
746
+ message=self.get_metrics(),
747
+ )
747
748
 
748
749
  def get_metrics(self):
749
750
  cpu_percent = psutil.cpu_percent(interval=1, percpu=False)
primitive/jobs/actions.py CHANGED
@@ -54,6 +54,7 @@ class Jobs(BaseAction):
54
54
  jobs = [edge["node"] for edge in result.data["jobs"]["edges"]]
55
55
  return jobs
56
56
 
57
+ @guard
57
58
  def get_job_runs(
58
59
  self,
59
60
  organization_id: Optional[str] = None,
@@ -67,7 +67,7 @@ class Monitor(BaseAction):
67
67
  # handles cleanup of old reservations
68
68
  # obtains an active JobRun's ID
69
69
  if not RUNNING_IN_CONTAINER:
70
- self.primitive.hardware.push_metrics()
70
+ # self.primitive.hardware.push_metrics()
71
71
 
72
72
  hardware = self.primitive.hardware.get_own_hardware_details()
73
73
  # fetch the latest hardware and activeReservation details
@@ -8,7 +8,8 @@ from ..utils.auth import guard
8
8
  from .graphql.queries import authorized_keys_query
9
9
 
10
10
  HOME_DIRECTORY = Path.home()
11
- AUTHORIZED_KEYS_FILEPATH = Path(HOME_DIRECTORY / ".ssh" / "authorized_keys")
11
+ SSH_DIR = Path(HOME_DIRECTORY / ".ssh")
12
+ AUTHORIZED_KEYS_FILEPATH = SSH_DIR.joinpath("authorized_keys")
12
13
 
13
14
 
14
15
  class Provisioning(BaseAction):
@@ -24,8 +25,9 @@ class Provisioning(BaseAction):
24
25
  return result.data["authorizedKeys"]
25
26
 
26
27
  def add_reservation_authorized_keys(self, reservation_id: str) -> None:
27
- AUTHORIZED_KEYS_BACKUP_FILEPATH = Path(
28
- HOME_DIRECTORY / ".ssh" / f"authorized_keys.bak-{reservation_id}"
28
+ SSH_DIR.mkdir(parents=True, exist_ok=True)
29
+ AUTHORIZED_KEYS_BACKUP_FILEPATH = SSH_DIR.joinpath(
30
+ f"authorized_keys.bak-{reservation_id}"
29
31
  )
30
32
 
31
33
  if AUTHORIZED_KEYS_FILEPATH.exists():
@@ -47,8 +49,8 @@ class Provisioning(BaseAction):
47
49
  # self.primitive.sshd.reload()
48
50
 
49
51
  def remove_reservation_authorized_keys(self, reservation_id: str) -> None:
50
- AUTHORIZED_KEYS_BACKUP_FILEPATH = Path(
51
- HOME_DIRECTORY / ".ssh" / f"authorized_keys.bak-{reservation_id}"
52
+ AUTHORIZED_KEYS_BACKUP_FILEPATH = SSH_DIR.joinpath(
53
+ f"authorized_keys.bak-{reservation_id}"
52
54
  )
53
55
 
54
56
  if AUTHORIZED_KEYS_BACKUP_FILEPATH.exists():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primitive
3
- Version: 0.2.59
3
+ Version: 0.2.61
4
4
  Project-URL: Documentation, https://github.com//primitivecorp/primitive-cli#readme
5
5
  Project-URL: Issues, https://github.com//primitivecorp/primitive-cli/issues
6
6
  Project-URL: Source, https://github.com//primitivecorp/primitive-cli
@@ -1,4 +1,4 @@
1
- primitive/__about__.py,sha256=DQFleay9CHBMqSyleA7yap__uZps7tWqIeLhtV6rCvw,130
1
+ primitive/__about__.py,sha256=44bjP-EhQtVr_s6llvOiTQ56lIx7zqJ9wHAGO3s_eCw,130
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
3
  primitive/cli.py,sha256=g7EtHI9MATAB0qQu5w-WzbXtxz_8zu8z5E7sETmMkKU,2509
4
4
  primitive/client.py,sha256=gyZIj61qMtOi_s8y0WG3gDehGT-Ms3BtbDP2J_2lajU,3753
@@ -23,10 +23,11 @@ primitive/exec/actions.py,sha256=4d_TCjNDcVFoZ9Zw7ZuBa6hKMv2Xzm7_UX_8wcX1aSk,412
23
23
  primitive/exec/commands.py,sha256=66LO2kkJC-ynNZQpUCXv4Ol15QoacdSZAHblePDcmLo,510
24
24
  primitive/exec/interactive.py,sha256=TscY6s2ZysijidKPheq6y-fCErUVLS0zcdTW8XyFWGI,2435
25
25
  primitive/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- primitive/files/actions.py,sha256=jvsBivYBmPeqb6Ge7gECm_x20AFUL7UYPGJJFmoCeOM,12409
27
- primitive/files/commands.py,sha256=ZNW4y8JZF1li7P5ej1r-Xcqu0iGpRRlMYvthuZOLLbQ,1163
26
+ primitive/files/actions.py,sha256=tuf5PXgNrcxegfzC-CZ6r3iWBkQV9gxZpALU-hjyGpY,14865
27
+ primitive/files/commands.py,sha256=-U0ArpZXDWltmkELG2SYIOLbaiMC6Zv24X9zZ29aqCE,3021
28
+ primitive/files/ui.py,sha256=lYnfu6gnZ5f-C28wps_egVDz8tdjL5P4361uxpSyfvY,919
28
29
  primitive/files/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- primitive/files/graphql/fragments.py,sha256=II6WHZjzSqX4IELwdiWokqHTKvDq6mMHF5gp3rLnj3U,231
30
+ primitive/files/graphql/fragments.py,sha256=h_Gfi1a3o1_tTJfvW_HUvWmeo3VSSMcj38ObcUv7X7c,253
30
31
  primitive/files/graphql/mutations.py,sha256=Da_e6WSp-fsCYVE9A6SGkIQy9WDzjeQycNyHEn7vJqE,935
31
32
  primitive/files/graphql/queries.py,sha256=_ky-IRz928sKeSJuqaggTPxV4CGgmho3OyaAFu1z7nw,397
32
33
  primitive/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -39,7 +40,7 @@ primitive/graphql/relay.py,sha256=bmij2AjdpURQ6GGVCxwWhauF-r_SxuAU2oJ4sDbLxpI,72
39
40
  primitive/graphql/sdk.py,sha256=dE4TD8KiTKw3Y0uiw5XrIcuZGqexE47eSlPaPD6jDGo,1545
40
41
  primitive/graphql/utility_fragments.py,sha256=uIjwILC4QtWNyO5vu77VjQf_p0jvP3A9q_6zRq91zqs,303
41
42
  primitive/hardware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- primitive/hardware/actions.py,sha256=qDbwd8W6zMlEfp1IfxTWiyAFqV5iYomDisGVXTRfy8Y,29145
43
+ primitive/hardware/actions.py,sha256=4xswcLaHk4IhEwRK5Pw_rpEgn6XciT5K2apnHi8_cI4,29204
43
44
  primitive/hardware/android.py,sha256=tu7pBPxWFrIwb_mm5CEdFFf1_veNDOKjOCQg13i_Lh4,2758
44
45
  primitive/hardware/commands.py,sha256=lwO-cm3doGtuGvR30fib-nALJyNCScdCTXOVVbYVpkY,4604
45
46
  primitive/hardware/ui.py,sha256=12rucuZ2s-w5R4bKyxON5dEbrdDnVf5sbj3K_nbdo44,2473
@@ -48,7 +49,7 @@ primitive/hardware/graphql/fragments.py,sha256=H315uv-ujOsZEbQR5tmstbRU_9OCa5SEr
48
49
  primitive/hardware/graphql/mutations.py,sha256=wOKNJN-1x_DGxvdHt2Bm3AUqyW8P1lNPkxLIQThbXUg,1874
49
50
  primitive/hardware/graphql/queries.py,sha256=I86uLuOSjHSph11Y5MVCYko5Js7hoiEZ-cEoPTc4J-k,1392
50
51
  primitive/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- primitive/jobs/actions.py,sha256=Fej-rpdfIiHjs0SEFY2ZcMn64AWHn6kD90RC5wtRM3Q,6531
52
+ primitive/jobs/actions.py,sha256=-IRNO7KM63cPAbGRNQycyFsugW9hAyg-TDuK0g5AiSw,6542
52
53
  primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
53
54
  primitive/jobs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  primitive/jobs/graphql/fragments.py,sha256=rWZWxZs9ZWjWal0eiY_XPWUS7i7f3fwSM_w8ZvDs2JQ,614
@@ -56,7 +57,7 @@ primitive/jobs/graphql/mutations.py,sha256=8ASvCmwQh7cMeeiykOdYaYVryG8FRIuVF6v_J
56
57
  primitive/jobs/graphql/queries.py,sha256=ZxNmm-WovytbggNuKRnwa0kc26T34_0yhqkoqx-2uj0,1736
57
58
  primitive/messaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
59
  primitive/messaging/provider.py,sha256=NCgl1KiV4Ow49Laz2grNGsP7Ij4vUUeorUjkhAKF1H4,4182
59
- primitive/monitor/actions.py,sha256=vdjfA-3v1j4g-S-gs6mziE3pDf3eR6dtYtgEVRnSfBQ,9641
60
+ primitive/monitor/actions.py,sha256=AZOoNzASorvye5309h6BJbsup_jR8YunQ19EiG5SAQg,9643
60
61
  primitive/monitor/commands.py,sha256=VDlEL_Qpm_ysHxug7VpI0cVAZ0ny6AS91Y58D7F1zkU,409
61
62
  primitive/organizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
63
  primitive/organizations/actions.py,sha256=kVHOhG1oS2sI5p8uldSo5L-RUZsnG36eaulVuKLyZ-M,1863
@@ -73,7 +74,7 @@ primitive/projects/graphql/fragments.py,sha256=02F5nyI8i-ML_bV5FFHUgFWM5bBBfjmz_
73
74
  primitive/projects/graphql/mutations.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
75
  primitive/projects/graphql/queries.py,sha256=nFaVf6YOHA2L_FTgIUdRK-80hYTmv1a1X5ac7QPMp1k,646
75
76
  primitive/provisioning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- primitive/provisioning/actions.py,sha256=IYZYAbtomtZtlkqDaBxx4e7PFKGkRNqek_tABH6q_zY,2116
77
+ primitive/provisioning/actions.py,sha256=oM19p1JyqwPhfJtyy23RqhuIKinorTo78yOYWVLC98c,2165
77
78
  primitive/provisioning/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
79
  primitive/provisioning/graphql/queries.py,sha256=cBtuKa6shoatYZfKSnQoPJP6B8g8y3QhFqJ_pkvMcG0,134
79
80
  primitive/reservations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -98,8 +99,8 @@ primitive/utils/psutil.py,sha256=xa7ef435UL37jyjmUPbEqCO2ayQMpCs0HCrxVEvLcuM,763
98
99
  primitive/utils/shell.py,sha256=Z4zxmOaSyGCrS0D6I436iQci-ewHLt4UxVg1CD9Serc,2171
99
100
  primitive/utils/text.py,sha256=XiESMnlhjQ534xE2hMNf08WehE1SKaYFRNih0MmnK0k,829
100
101
  primitive/utils/x509.py,sha256=HwHRPqakTHWd40ny-9O_yNknSL1Cxo50O0UCjXHFq04,3796
101
- primitive-0.2.59.dist-info/METADATA,sha256=TK2BJRsrYXbcffM-S0oA9sBT03PjeiLEeDBwlaH1m6s,3540
102
- primitive-0.2.59.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
103
- primitive-0.2.59.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
104
- primitive-0.2.59.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
105
- primitive-0.2.59.dist-info/RECORD,,
102
+ primitive-0.2.61.dist-info/METADATA,sha256=VMscEolFKYI028E5OFCQY8wouk_Zw0eBzU1vXSCjwBA,3540
103
+ primitive-0.2.61.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
104
+ primitive-0.2.61.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
105
+ primitive-0.2.61.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
106
+ primitive-0.2.61.dist-info/RECORD,,