primitive 0.1.92__py3-none-any.whl → 0.1.94__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: 2024-present Dylan Stein <dylan@primitive.tech>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.92"
4
+ __version__ = "0.1.94"
@@ -17,6 +17,13 @@ class Agent(BaseAction):
17
17
  self,
18
18
  ):
19
19
  logger.enable("primitive")
20
+ logger.remove()
21
+ logger.add(
22
+ sink=sys.stderr,
23
+ # catch=True,
24
+ backtrace=True,
25
+ diagnose=True,
26
+ )
20
27
  logger.info(" [*] primitive")
21
28
  logger.info(f" [*] Version: {__version__}")
22
29
 
@@ -34,8 +41,8 @@ class Agent(BaseAction):
34
41
  # setting is_available of the parent also effects the children,
35
42
  # which may have active reservations as well
36
43
  self.primitive.hardware.check_in_http(is_online=True)
37
- except Exception as ex:
38
- logger.error(f"Error checking in hardware: {ex}")
44
+ except Exception as exception:
45
+ logger.exception(f"Error checking in hardware: {exception}")
39
46
  sys.exit(1)
40
47
 
41
48
  try:
@@ -43,10 +50,14 @@ class Agent(BaseAction):
43
50
  active_reservation_pk = None
44
51
 
45
52
  while True:
53
+ logger.debug("Syncing children...")
54
+ self.primitive.hardware._sync_children()
55
+
46
56
  logger.debug("Scanning for files to upload...")
47
57
  uploader.scan()
48
58
 
49
59
  hardware = self.primitive.hardware.get_own_hardware_details()
60
+
50
61
  if hardware["activeReservation"]:
51
62
  if (
52
63
  hardware["activeReservation"]["id"] != active_reservation_id
@@ -125,14 +136,23 @@ class Agent(BaseAction):
125
136
  )
126
137
  )
127
138
 
128
- downloaded_git_repository_dir = (
129
- self.primitive.git.download_git_repository_at_ref(
130
- git_repo_full_name=git_repo_full_name,
131
- git_ref=git_ref,
132
- github_access_token=github_access_token,
133
- destination=cache_dir,
139
+ try:
140
+ downloaded_git_repository_dir = (
141
+ self.primitive.git.download_git_repository_at_ref(
142
+ git_repo_full_name=git_repo_full_name,
143
+ git_ref=git_ref,
144
+ github_access_token=github_access_token,
145
+ destination=cache_dir,
146
+ )
134
147
  )
135
- )
148
+ except Exception as exception:
149
+ logger.error(
150
+ f"Error downloading source: {exception}"
151
+ )
152
+ self.primitive.jobs.job_run_update(
153
+ job_run["id"], status="request_completed", conclusion="failure"
154
+ )
155
+ continue
136
156
 
137
157
  source_dir = downloaded_git_repository_dir.joinpath(
138
158
  job_run["jobSettings"]["rootDirectory"]
@@ -146,9 +166,11 @@ class Agent(BaseAction):
146
166
  job_run=job_run,
147
167
  max_log_size=500 * 1024,
148
168
  )
149
- except Exception as e:
169
+ except Exception as exception:
150
170
  # Log Error
151
- logger.error(f"Error initializing agent runner: {e}")
171
+ logger.exception(
172
+ f"Error initializing agent runner: {exception}"
173
+ )
152
174
  else:
153
175
  # Execute job
154
176
  runner.execute()
primitive/agent/runner.py CHANGED
@@ -1,17 +1,18 @@
1
1
  import os
2
2
  import threading
3
3
  import typing
4
+ from enum import IntEnum
4
5
  from pathlib import Path, PurePath
5
6
  from time import sleep
6
- from typing import Dict, Iterable, List, Optional, TypedDict, Callable
7
- from enum import IntEnum
7
+ from typing import Callable, Dict, Iterable, List, Optional, TypedDict
8
8
 
9
9
  import yaml
10
10
  from loguru import logger
11
- from .process import Process
12
- from .provision import ProvisionPython
11
+
13
12
  from ..utils.cache import get_artifacts_cache, get_logs_cache
14
13
  from ..utils.files import find_files_for_extension
14
+ from .process import Process
15
+ from .provision import ProvisionPython
15
16
 
16
17
  try:
17
18
  from yaml import CLoader as Loader
@@ -74,6 +75,8 @@ class AgentRunner:
74
75
  Path(get_logs_cache(self.job_id) / log_name),
75
76
  rotation=self.max_log_size,
76
77
  format=AgentRunner.log_serializer(),
78
+ backtrace=True,
79
+ diagnose=True,
77
80
  )
78
81
 
79
82
  logger.info(f"Scanning directory for job file {self.job_slug}")
@@ -165,8 +168,10 @@ class AgentRunner:
165
168
 
166
169
  try:
167
170
  proc.start()
168
- except Exception as e:
169
- logger.error(f"Error while attempting to run process {e}")
171
+ except Exception as exception:
172
+ logger.exception(
173
+ f"Error while attempting to run process {exception}"
174
+ )
170
175
  self.primitive.jobs.job_run_update(
171
176
  self.job_id, status="request_completed", conclusion="failure"
172
177
  )
@@ -42,12 +42,7 @@ from primitive.utils.shell import does_executable_exist
42
42
  class Hardware(BaseAction):
43
43
  def __init__(self, *args, **kwargs) -> None:
44
44
  super().__init__(*args, **kwargs)
45
- self.previous_state = {
46
- "isHealthy": False,
47
- "isQuarantined": False,
48
- "isAvailable": False,
49
- "isOnline": False,
50
- }
45
+ self.status_cache = {}
51
46
  self.children = []
52
47
 
53
48
  def _get_darwin_system_profiler_values(self) -> Dict[str, str]:
@@ -81,7 +76,7 @@ class Hardware(BaseAction):
81
76
  )
82
77
  except subprocess.CalledProcessError as exception:
83
78
  message = f"Error running system_profiler: {exception}"
84
- logger.error(message)
79
+ logger.exception(message)
85
80
  return supported_metal_device
86
81
 
87
82
  try:
@@ -90,7 +85,7 @@ class Hardware(BaseAction):
90
85
  )
91
86
  except json.JSONDecodeError as exception:
92
87
  message = f"Error decoding JSON: {exception}"
93
- logger.error(message)
88
+ logger.exception(message)
94
89
  return supported_metal_device
95
90
 
96
91
  # Checks if any attached displays have metal support
@@ -121,7 +116,7 @@ class Hardware(BaseAction):
121
116
  )
122
117
  except subprocess.CalledProcessError as exception:
123
118
  message = f"Command {nvidia_smi_query_gpu_csv_command} failed with exception: {exception}" # noqa
124
- logger.error(message)
119
+ logger.exception(message)
125
120
  raise exception
126
121
 
127
122
  try:
@@ -133,7 +128,7 @@ class Hardware(BaseAction):
133
128
  )
134
129
  except UnicodeDecodeError as exception:
135
130
  message = f"Error decoding: {exception}"
136
- logger.error(message)
131
+ logger.exception(message)
137
132
  raise exception
138
133
 
139
134
  nvidia_smi_query_gpu_csv_dict_reader = csv.DictReader(
@@ -164,7 +159,7 @@ class Hardware(BaseAction):
164
159
  )
165
160
  except subprocess.CalledProcessError as exception:
166
161
  message = f"Error running {system_profiler_hardware_data_type_command}: {exception}" # noqa
167
- logger.error(message)
162
+ logger.exception(message)
168
163
  raise exception
169
164
 
170
165
  try:
@@ -173,7 +168,7 @@ class Hardware(BaseAction):
173
168
  )
174
169
  except json.JSONDecodeError as exception:
175
170
  message = f"Error decoding JSON: {exception}" # noqa
176
- logger.error(message)
171
+ logger.exception(message)
177
172
  raise exception
178
173
 
179
174
  metal_device_json = system_profiler_hardware_data_type_json[
@@ -373,7 +368,7 @@ class Hardware(BaseAction):
373
368
  )
374
369
  except client_exceptions.ClientConnectorError as exception:
375
370
  message = " [*] Failed to update hardware system info! "
376
- logger.error(message)
371
+ logger.exception(message)
377
372
  raise exception
378
373
 
379
374
  message = " [*] Updated hardware system info successfully! "
@@ -388,8 +383,11 @@ class Hardware(BaseAction):
388
383
  is_quarantined: bool = False,
389
384
  is_available: bool = False,
390
385
  is_online: bool = True,
386
+ fingerprint: Optional[str] = None,
391
387
  ):
392
- fingerprint = self.primitive.host_config.get("fingerprint", None)
388
+ # if no fingerprint supplied from argument try from the host_config
389
+ if not fingerprint:
390
+ fingerprint = self.primitive.host_config.get("fingerprint", None)
393
391
 
394
392
  if not fingerprint:
395
393
  message = (
@@ -421,12 +419,22 @@ class Hardware(BaseAction):
421
419
  logger.debug(message.get("message"))
422
420
 
423
421
  if checkin_success:
424
- previous_state = self.previous_state
425
- self.previous_state = new_state.copy()
426
-
427
- message = " [*] Checked in successfully: "
422
+ if self.status_cache.get(fingerprint):
423
+ previous_status = self.status_cache[fingerprint]
424
+ else:
425
+ previous_status = {
426
+ "isHealthy": False,
427
+ "isQuarantined": False,
428
+ "isAvailable": False,
429
+ "isOnline": False,
430
+ }
431
+ self.status_cache[fingerprint] = new_state.copy()
432
+
433
+ message = f" [*] Checked in successfully for {fingerprint}: "
434
+ is_new_status = False
428
435
  for key, value in new_state.items():
429
- if value != previous_state.get(key, None):
436
+ if value != previous_status.get(key, None):
437
+ is_new_status = True
430
438
  if value is True:
431
439
  message = (
432
440
  message
@@ -441,6 +449,8 @@ class Hardware(BaseAction):
441
449
  + click.style("✅")
442
450
  + click.style(" ==> 💤 ", fg="yellow")
443
451
  )
452
+ if is_new_status is False:
453
+ message += "No changes."
444
454
  logger.info(message)
445
455
  else:
446
456
  message = "Failed to check in!"
@@ -448,7 +458,7 @@ class Hardware(BaseAction):
448
458
  return result
449
459
  except client_exceptions.ClientConnectorError as exception:
450
460
  message = " [*] Failed to check in! "
451
- logger.error(message)
461
+ logger.exception(message)
452
462
  raise exception
453
463
 
454
464
  @guard
@@ -512,7 +522,8 @@ class Hardware(BaseAction):
512
522
 
513
523
  def get_own_hardware_details(self):
514
524
  hardware_list_result = self.get_hardware_list(
515
- fingerprint=self.primitive.host_config.get("fingerprint")
525
+ fingerprint=self.primitive.host_config.get("fingerprint"),
526
+ nested_children=True,
516
527
  )
517
528
  hardware = (
518
529
  hardware_list_result.data.get("hardwareList").get("edges")[0].get("node")
@@ -582,4 +593,68 @@ class Hardware(BaseAction):
582
593
  # get the latest children from the node
583
594
  # compare the two and update the node with the latest children
584
595
  # remove any children from remote that are not in the latest children
585
- pass
596
+ hardware = self.primitive.hardware.get_own_hardware_details()
597
+ remote_children = hardware.get("children", [])
598
+ local_children = self.primitive.hardware._list_local_children()
599
+
600
+ new_child_registered = False
601
+ # need to register new children that were not previously registered
602
+ if len(remote_children) < len(local_children):
603
+ logger.info("New children found.")
604
+ for local_child in local_children:
605
+ found = False
606
+ for remote_child in remote_children:
607
+ if remote_child["slug"] == local_child.slug:
608
+ found = True
609
+ break
610
+ if not found:
611
+ try:
612
+ logger.info(f"Registering new child: {local_child.slug}")
613
+ self.primitive.hardware.register_child(child=local_child)
614
+ new_child_registered = True
615
+ except Exception as exception:
616
+ logger.exception(f"Error registering new children: {exception}")
617
+
618
+ # if a new child was registered, get the fresh state of the world
619
+ if new_child_registered:
620
+ hardware = self.primitive.hardware.get_own_hardware_details()
621
+ remote_children = hardware.get("children", [])
622
+
623
+ try:
624
+ # need to check in children that had been previously registered
625
+ # TODO: this is where, once you've got the local children you run a
626
+ # predefined health check. online != healthy
627
+ for remote_child in remote_children:
628
+ # if the remote_child is not in the local_children, then it is offline
629
+ found = False
630
+ for local_child in local_children:
631
+ if remote_child["slug"] == local_child.slug:
632
+ is_available = True
633
+ if remote_child["activeReservation"] is not None:
634
+ if (
635
+ remote_child["activeReservation"]["id"]
636
+ and remote_child["isAvailable"]
637
+ ):
638
+ is_available = False
639
+
640
+ self.primitive.hardware.check_in_http(
641
+ is_available=is_available,
642
+ is_healthy=True,
643
+ is_online=True,
644
+ fingerprint=remote_child["fingerprint"],
645
+ )
646
+ found = True
647
+ break
648
+ if not found:
649
+ logger.info(
650
+ f"Remote child {remote_child['slug']}, not found in local children. Setting offline."
651
+ )
652
+ self.primitive.hardware.check_in_http(
653
+ is_available=False,
654
+ is_healthy=False,
655
+ is_online=False,
656
+ fingerprint=remote_child["fingerprint"],
657
+ )
658
+
659
+ except Exception as exception:
660
+ logger.exception(f"Error checking in children: {exception}")
@@ -2,9 +2,12 @@ from dataclasses import dataclass, field
2
2
  from subprocess import PIPE, Popen
3
3
  from typing import Dict
4
4
 
5
+ from primitive.utils.text import slugify
6
+
5
7
 
6
8
  @dataclass
7
9
  class AndroidDevice:
10
+ slug: str
8
11
  serial: str
9
12
  usb: str
10
13
  product: str
@@ -47,7 +50,9 @@ def list_devices():
47
50
  detail for detail in line.split(" ") if detail != ""
48
51
  ]
49
52
 
53
+ slug = slugify(device_details_array[0])
50
54
  android_device = AndroidDevice(
55
+ slug=slug,
51
56
  serial=device_details_array[0],
52
57
  usb=device_details_array[1],
53
58
  product=device_details_array[2],
@@ -87,26 +87,7 @@ def hardware_status_string(hardware):
87
87
  return "Available"
88
88
 
89
89
 
90
- @cli.command("list")
91
- @click.pass_context
92
- def list_command(context):
93
- """List Hardware"""
94
- primitive: Primitive = context.obj.get("PRIMITIVE")
95
- get_hardware_list_result = primitive.hardware.get_hardware_list(
96
- nested_children=True
97
- )
98
- message = get_hardware_list_result.data
99
-
100
- hardware_list = [
101
- hardware.get("node")
102
- for hardware in get_hardware_list_result.data.get("hardwareList").get("edges")
103
- ]
104
-
105
- message = hardware_list
106
- if context.obj["JSON"]:
107
- print_result(message=message, context=context)
108
- return
109
-
90
+ def render_hardware_table(hardware_list):
110
91
  console = Console()
111
92
 
112
93
  table = Table(show_header=True, header_style="bold magenta")
@@ -153,3 +134,45 @@ def list_command(context):
153
134
  )
154
135
 
155
136
  console.print(table)
137
+
138
+
139
+ @cli.command("list")
140
+ @click.pass_context
141
+ def list_command(context):
142
+ """List Hardware"""
143
+ primitive: Primitive = context.obj.get("PRIMITIVE")
144
+ get_hardware_list_result = primitive.hardware.get_hardware_list(
145
+ nested_children=True
146
+ )
147
+
148
+ hardware_list = [
149
+ hardware.get("node")
150
+ for hardware in get_hardware_list_result.data.get("hardwareList").get("edges")
151
+ ]
152
+
153
+ if context.obj["JSON"]:
154
+ print_result(message=hardware_list, context=context)
155
+ return
156
+ else:
157
+ render_hardware_table(hardware_list)
158
+
159
+
160
+ @cli.command("get")
161
+ @click.pass_context
162
+ @click.argument(
163
+ "hardware_identifier",
164
+ type=str,
165
+ required=True,
166
+ )
167
+ def get_command(context, hardware_identifier: str) -> None:
168
+ """Get Hardware"""
169
+ primitive: Primitive = context.obj.get("PRIMITIVE")
170
+ hardware = primitive.hardware.get_hardware_from_slug_or_id(
171
+ hardware_identifier=hardware_identifier
172
+ )
173
+
174
+ if context.obj["JSON"]:
175
+ print_result(message=hardware, context=context)
176
+ return
177
+ else:
178
+ render_hardware_table([hardware])
@@ -55,6 +55,7 @@ query hardwareList(
55
55
  ...HardwareFragment
56
56
  children {
57
57
  ...HardwareFragment
58
+ fingerprint
58
59
  }
59
60
  }
60
61
  }
@@ -88,6 +89,7 @@ query hardwareDetails(
88
89
  ...HardwareFragment
89
90
  children {
90
91
  ...HardwareFragment
92
+ fingerprint
91
93
  }
92
94
  }
93
95
  }
@@ -0,0 +1,24 @@
1
+ import re
2
+ import unicodedata
3
+
4
+
5
+ # from Django for consistency
6
+ # https://github.com/django/django/blob/stable/5.1.x/django/utils/text.py#L452
7
+ def slugify(value, allow_unicode=False):
8
+ """
9
+ Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
10
+ dashes to single dashes. Remove characters that aren't alphanumerics,
11
+ underscores, or hyphens. Convert to lowercase. Also strip leading and
12
+ trailing whitespace, dashes, and underscores.
13
+ """
14
+ value = str(value)
15
+ if allow_unicode:
16
+ value = unicodedata.normalize("NFKC", value)
17
+ else:
18
+ value = (
19
+ unicodedata.normalize("NFKD", value)
20
+ .encode("ascii", "ignore")
21
+ .decode("ascii")
22
+ )
23
+ value = re.sub(r"[^\w\s-]", "", value.lower())
24
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primitive
3
- Version: 0.1.92
3
+ Version: 0.1.94
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,13 +1,13 @@
1
- primitive/__about__.py,sha256=zAovHlidIiOORWtwXziyiE6_7Yij_H9sOCJqsVE2txc,130
1
+ primitive/__about__.py,sha256=KXegEIne06p7p51yURPxgSDL9t-28iyLfXrBT-skS_Q,130
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
3
  primitive/cli.py,sha256=CiI60bG3UZyNFuLTpchr0KeJRG5SALj455Ob11CegGE,2412
4
4
  primitive/client.py,sha256=PPyIQRvKKSqCF9RRF5mJJ4Vqqolpzy1YXqffNLKIvAA,2390
5
5
  primitive/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- primitive/agent/actions.py,sha256=Ay76qUwMYNERndA0lAvwK8zjVOrR8nMmSi-NeSwIlA8,6817
6
+ primitive/agent/actions.py,sha256=s9hOVKsgjwt7iXnpGOpoR5Ctxh9g7g-g1xu5QjtvPbc,7636
7
7
  primitive/agent/commands.py,sha256=-dVDilELfkGfbZB7qfEPs77Dm1oT62qJj4tsIk4KoxI,254
8
8
  primitive/agent/process.py,sha256=32eoj0W1-LG-9xxeHia-jk9jTah1cnmjCYnvczgXYGU,3538
9
9
  primitive/agent/provision.py,sha256=rmwnro1K5F8mwtd45XAq7RVQmpDWnbBCQ8X_qgWhm3M,1546
10
- primitive/agent/runner.py,sha256=B_M0BOeCy7gO7Mp6nfxv-FJ-9yBbujGVNoZziAMcDjc,9357
10
+ primitive/agent/runner.py,sha256=gVr-IWizw7UiP-9vgDviGzmrJBi5jLMXMw-0aVusPIs,9487
11
11
  primitive/agent/uploader.py,sha256=OkgwXhWKoECOJnW_ZmpzmUS_cpb-orC_uebNcmf5byw,2948
12
12
  primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  primitive/auth/actions.py,sha256=MPsG9LcKcOPwA7gZ9Ewk0PZJhTQvIrGfODdz4GxSzgA,999
@@ -40,13 +40,13 @@ primitive/graphql/relay.py,sha256=bmij2AjdpURQ6GGVCxwWhauF-r_SxuAU2oJ4sDbLxpI,72
40
40
  primitive/graphql/sdk.py,sha256=DBFH8vw8FAGvRy8_FZc9WcjnwaQDlXmI8fiYmhCg-b0,1458
41
41
  primitive/graphql/utility_fragments.py,sha256=uIjwILC4QtWNyO5vu77VjQf_p0jvP3A9q_6zRq91zqs,303
42
42
  primitive/hardware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- primitive/hardware/actions.py,sha256=6dnPUN_hjXy4DIVoKWneiqgdF6UWfwDN82GczAqRykw,22018
44
- primitive/hardware/android.py,sha256=NcvbDb9ZLl5PCeHII84yLxZo0V48Vn5K75AzkIsg9is,2623
45
- primitive/hardware/commands.py,sha256=sU9xES9UK3JO1RxZMeC0412pcWIwXWXwr8rmMzz1VGQ,5088
43
+ primitive/hardware/actions.py,sha256=tmsr50UPz_xFXPdeSa89e7NgDlw9vMwqWUUXlFGzmQs,25775
44
+ primitive/hardware/android.py,sha256=tu7pBPxWFrIwb_mm5CEdFFf1_veNDOKjOCQg13i_Lh4,2758
45
+ primitive/hardware/commands.py,sha256=cWl8j0XtkXQtPHtpO_C6Gh0-dnAEysOFryf3VnDOTOo,5650
46
46
  primitive/hardware/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  primitive/hardware/graphql/fragments.py,sha256=kI6qnTNjaEaUr-C6eD55COphtueVYbYOWZwN5EW_3qw,350
48
48
  primitive/hardware/graphql/mutations.py,sha256=_4Hkbfik9Ron4T-meulu6T-9FR_BZjyPNwn745MPksU,1484
49
- primitive/hardware/graphql/queries.py,sha256=2k-ZMaSDDO8YsbYNV54pw0yHnsPco6Y9ZfKBOEnQCsw,1348
49
+ primitive/hardware/graphql/queries.py,sha256=I86uLuOSjHSph11Y5MVCYko5Js7hoiEZ-cEoPTc4J-k,1392
50
50
  primitive/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  primitive/jobs/actions.py,sha256=CtyO-Z9614TgIoXJJX1QGsoll0fgpBIjG9PJH5JwCQs,4901
52
52
  primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
@@ -90,9 +90,10 @@ primitive/utils/git.py,sha256=1qNOu8X-33CavmrD580BmrFhD_WVO9PGWHUUboXJR_g,663
90
90
  primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
91
91
  primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
92
92
  primitive/utils/shell.py,sha256=vpjr2Y7UQGYOvPGa6_RYXPPjqScfa9k7kT3tugF9h4Y,1837
93
+ primitive/utils/text.py,sha256=XiESMnlhjQ534xE2hMNf08WehE1SKaYFRNih0MmnK0k,829
93
94
  primitive/utils/verible.py,sha256=Zb5NUISvcaIgEvgCDBWr-GCoceMa79Tcwvr5Wl9lfnA,2252
94
- primitive-0.1.92.dist-info/METADATA,sha256=zFeFQOxPZQ24o1jc-GLNXTy4jK97IhPIspA2NKTX4Y4,3670
95
- primitive-0.1.92.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- primitive-0.1.92.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
97
- primitive-0.1.92.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
98
- primitive-0.1.92.dist-info/RECORD,,
95
+ primitive-0.1.94.dist-info/METADATA,sha256=jF78rkKKUBDKv-30b--nmW4RsQfUIPdzOkWVoD7HUpE,3670
96
+ primitive-0.1.94.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ primitive-0.1.94.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
98
+ primitive-0.1.94.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
99
+ primitive-0.1.94.dist-info/RECORD,,