primitive 0.1.59__py3-none-any.whl → 0.1.60__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.59"
4
+ __version__ = "0.1.60"
@@ -129,6 +129,7 @@ class Agent(BaseAction):
129
129
  source_dir=source_dir,
130
130
  job_id=job_run["id"],
131
131
  job_slug=job_run["job"]["slug"],
132
+ max_log_size=500 * 1024,
132
133
  )
133
134
  except Exception as e:
134
135
  # Log Error
@@ -34,7 +34,7 @@ class Process:
34
34
  self.sel.register(self.process.stdout, selectors.EVENT_READ)
35
35
  self.sel.register(self.process.stderr, selectors.EVENT_READ)
36
36
 
37
- def probe_logs(self):
37
+ def log(self):
38
38
  for key, _ in self.sel.select():
39
39
  data = key.fileobj.readline()
40
40
  if not data:
@@ -48,7 +48,7 @@ class Process:
48
48
 
49
49
  def wait(self):
50
50
  while True:
51
- self.probe_logs()
51
+ self.log()
52
52
  if not self.is_running():
53
53
  break
54
54
 
@@ -2,6 +2,7 @@ import sys
2
2
  from subprocess import Popen, PIPE
3
3
  from pathlib import Path
4
4
  from typing import Dict
5
+ from ..utils.shell import env_string_to_dict
5
6
 
6
7
 
7
8
  class ProvisionPython:
@@ -34,14 +35,7 @@ class ProvisionPython:
34
35
  output, _ = proc.communicate()
35
36
 
36
37
  # Split the output into lines and parse it into a dictionary
37
- env_vars = {}
38
-
39
- for line in output.splitlines():
40
- var_line = line.split("=", 1)
41
-
42
- if len(var_line) == 2:
43
- key, value = var_line
44
- env_vars[key] = value
38
+ env_vars = env_string_to_dict(output)
45
39
 
46
40
  cmd = f"python -m pip install -r {self.requirements_path}"
47
41
  proc = Popen(
primitive/agent/runner.py CHANGED
@@ -7,11 +7,10 @@ from typing import Dict, Iterable, List, Optional, TypedDict
7
7
 
8
8
  import yaml
9
9
  from loguru import logger
10
-
11
- from ..utils.cache import get_artifacts_cache
12
- from ..utils.files import find_files_for_extension
13
10
  from .process import Process
14
11
  from .provision import ProvisionPython
12
+ from ..utils.cache import get_artifacts_cache, get_logs_cache
13
+ from ..utils.files import find_files_for_extension
15
14
 
16
15
  try:
17
16
  from yaml import CLoader as Loader
@@ -56,12 +55,11 @@ class AgentRunner:
56
55
  self.job_slug = job_slug
57
56
  self.max_log_size = max_log_size
58
57
  self.artifacts_dir = get_artifacts_cache(self.job_id)
58
+ self.logs_dir = get_logs_cache(self.job_id)
59
+ self.logger_handle = None
59
60
 
60
61
  logger.enable("primitive")
61
- self.logger_handle = logger.add(
62
- Path(self.artifacts_dir / "runner_{time}.log"),
63
- rotation=self.max_log_size, # Rotate when the log file reaches 10MB
64
- )
62
+ self.swap_logs(label="init")
65
63
 
66
64
  logger.info(f"Scanning directory for job {self.job_slug}")
67
65
 
@@ -113,6 +111,9 @@ class AgentRunner:
113
111
  conclusion = None
114
112
  total_errors = 0
115
113
  for step in self.steps():
114
+ # Swap logger
115
+ self.swap_logs(label=step["name"])
116
+
116
117
  logger.info(f"Beginning step {step['name']}")
117
118
 
118
119
  # Update workdir
@@ -138,7 +139,7 @@ class AgentRunner:
138
139
  while proc.is_running():
139
140
  # Check job status
140
141
  status = self.primitive.jobs.get_job_status(self.job_id)
141
- status_value = status["jobRun"]["status"]
142
+ status_value = status.data["jobRun"]["status"]
142
143
 
143
144
  # TODO: Should probably use request_cancelled or something
144
145
  # once we change it, we'll have to call conclude w/ cancelled status
@@ -184,6 +185,15 @@ class AgentRunner:
184
185
  logger.info(f"Completed {self.job_slug} job")
185
186
  logger.remove(self.logger_handle)
186
187
 
188
+ def swap_logs(self, label: str):
189
+ # Remove Handle
190
+ if self.logger_handle:
191
+ logger.remove(self.logger_handle)
192
+
193
+ self.logger_handle = logger.add(
194
+ Path(self.logs_dir / f"{label}_{{time}}.primitive.log"), rotation=self.max_log_size
195
+ )
196
+
187
197
  def provision(self) -> Optional[Dict]:
188
198
  match self.job["provision"]:
189
199
  case "python":
@@ -199,8 +209,6 @@ class AgentRunner:
199
209
  return prov.create_env()
200
210
 
201
211
  def collect_artifacts(self, step: JobStep) -> None:
202
- # str(PurePath(file_path).relative_to(Path(source))
203
-
204
212
  # Search each artifact type
205
213
  for artifact in step["artifacts"]:
206
214
  files = find_files_for_extension(self.source_dir, artifact["extension"])
@@ -1,9 +1,10 @@
1
1
  import typing
2
+ from typing import Dict
2
3
  import shutil
3
4
  import os
4
5
  from loguru import logger
5
6
  from pathlib import Path, PurePath
6
- from ..utils.cache import get_artifacts_cache
7
+ from ..utils.cache import get_artifacts_cache, get_logs_cache
7
8
 
8
9
  if typing.TYPE_CHECKING:
9
10
  import primitive.client
@@ -21,57 +22,85 @@ class Uploader:
21
22
  path, key_prefix=prefix, job_run_id=job_run_id
22
23
  )
23
24
  return file_upload_response.json()["data"]["fileUpload"]["id"]
25
+
26
+ def upload_dir(self, cache: Path) -> Dict:
27
+ file_ids = []
28
+ job_run_id = cache.name
29
+
30
+ files = None
31
+ has_walk = getattr(cache, "walk", None)
32
+ if has_walk:
33
+ files = sorted(
34
+ [
35
+ current_path / file
36
+ for current_path, _, current_path_files in cache.walk()
37
+ for file in current_path_files
38
+ ],
39
+ key=lambda p: p.stat().st_size,
40
+ )
41
+ else:
42
+ files = sorted(
43
+ [
44
+ Path(Path(current_path) / file)
45
+ for current_path, _, current_path_files in os.walk(cache)
46
+ for file in current_path_files
47
+ ],
48
+ key=lambda p: p.stat().st_size,
49
+ )
50
+
51
+ for file in files:
52
+ upload_id = self.upload_file(
53
+ file,
54
+ prefix=str(PurePath(file).relative_to(cache.parent).parent),
55
+ job_run_id=job_run_id,
56
+ )
57
+
58
+ if upload_id:
59
+ file_ids.append(upload_id)
60
+ continue
61
+
62
+ logger.error(f"Unable to upload file {file}")
63
+
64
+ # Clean up job cache
65
+ shutil.rmtree(path=cache)
66
+
67
+ return {job_run_id: file_ids}
68
+
24
69
 
25
70
  def scan(self) -> None:
26
71
  # Scan artifacts directory
27
72
  artifacts_dir = get_artifacts_cache()
73
+ logs_dir = get_logs_cache()
28
74
 
29
- subdirs = sorted(
30
- [job_cache for job_cache in artifacts_dir.iterdir() if job_cache.is_dir()],
31
- key=lambda p: p.stat().st_ctime,
32
- )
75
+ artifacts = sorted([
76
+ artifacts_cache
77
+ for artifacts_cache in artifacts_dir.iterdir()
78
+ if artifacts_cache.is_dir()
79
+ ], key=lambda p: p.stat().st_ctime)
80
+
81
+ logs = sorted([
82
+ logs_cache
83
+ for logs_cache in logs_dir.iterdir()
84
+ if logs_cache.is_dir()
85
+ ], key=lambda p: p.stat().st_ctime)
86
+
87
+ log_files = {
88
+ job_id: files
89
+ for log_path in logs
90
+ for job_id, files in self.upload_dir(log_path).items()
91
+ }
92
+
93
+ artifact_files = {
94
+ job_id: files
95
+ for artifact_path in artifacts
96
+ for job_id, files in self.upload_dir(artifact_path).items()
97
+ }
98
+
99
+ files_by_id = {
100
+ job_id: log_files.get(job_id, []) + artifact_files.get(job_id, [])
101
+ for job_id in log_files.keys() | artifact_files.keys()
102
+ }
33
103
 
34
- for job_cache in subdirs:
35
- job_run_id = job_cache.name
36
-
37
- files = None
38
- has_walk = getattr(job_cache, "walk", None)
39
- if has_walk:
40
- files = sorted(
41
- [
42
- w_path / file
43
- for w_path, _, w_files in job_cache.walk()
44
- for file in w_files
45
- ],
46
- key=lambda p: p.stat().st_size,
47
- )
48
- else:
49
- files = sorted(
50
- [
51
- Path(Path(w_path) / file)
52
- for w_path, _, w_files in os.walk(job_cache)
53
- for file in w_files
54
- ],
55
- key=lambda p: p.stat().st_size,
56
- )
57
-
58
- file_ids = []
59
- for file in files:
60
- upload_id = self.upload_file(
61
- file,
62
- prefix=str(PurePath(file).relative_to(job_cache.parent).parent),
63
- job_run_id=job_run_id,
64
- )
65
-
66
- if upload_id:
67
- file_ids.append(upload_id)
68
- continue
69
-
70
- logger.error(f"Unable to upload file {file}")
71
-
72
- # Update job run
73
- if len(file_ids) > 0:
74
- self.primitive.jobs.job_run_update(id=job_run_id, file_ids=file_ids)
75
-
76
- # Clean up job cache
77
- shutil.rmtree(path=job_cache)
104
+ # Update job run
105
+ for job_id, files in files_by_id.items():
106
+ self.primitive.jobs.job_run_update(id=job_id, file_ids=files)
primitive/utils/cache.py CHANGED
@@ -44,6 +44,20 @@ def get_artifacts_cache(cache_id: str = None) -> Path:
44
44
  return artifacts_dir
45
45
 
46
46
 
47
+ def get_logs_cache(cache_id: str = None) -> Path:
48
+ cache_dir = get_cache_dir()
49
+
50
+ logs_dir = cache_dir / "logs"
51
+
52
+ if cache_id:
53
+ logs_dir = logs_dir / cache_id
54
+
55
+ if not logs_dir.exists():
56
+ logs_dir.mkdir(parents=True, exist_ok=True)
57
+
58
+ return logs_dir
59
+
60
+
47
61
  def get_deps_cache() -> Path:
48
62
  cache_dir = get_cache_dir()
49
63
 
primitive/utils/shell.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from pathlib import Path
2
2
  import subprocess
3
+ from typing import Dict
3
4
 
4
5
 
5
6
  def add_path_to_shell(path: Path):
@@ -31,3 +32,27 @@ def add_path_to_shell(path: Path):
31
32
  file.write(f"export PATH={path}:$PATH\n")
32
33
 
33
34
  return True
35
+
36
+
37
+ def env_string_to_dict(env_str: str) -> Dict:
38
+ lines = env_str.splitlines()
39
+
40
+ current_key = None
41
+ current_value = []
42
+ env_dict = {}
43
+ for line in lines:
44
+ if "=" in line:
45
+ if current_key is not None:
46
+ env_dict[current_key] = "\n".join(current_value)
47
+
48
+ key, value = line.split("=", 1)
49
+
50
+ current_key = key
51
+ current_value = [value]
52
+ else:
53
+ current_value.append(line)
54
+
55
+ if current_key is not None:
56
+ env_dict[current_key] = "\n".join(current_value)
57
+
58
+ return env_dict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: primitive
3
- Version: 0.1.59
3
+ Version: 0.1.60
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,14 +1,14 @@
1
- primitive/__about__.py,sha256=C9j2a-6ERCVy_C_OHbf9s91MGCcAIm1Dlc-h-vRuSew,130
1
+ primitive/__about__.py,sha256=ltk-zb4IMmLsM4NDisEz2bXWrL5uUzzqsV5yJM95Caw,130
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
3
  primitive/cli.py,sha256=CGmWiqqCLMHtHGOUPuf3tVO6VvChBZ1VdSwCCglnBgA,2582
4
4
  primitive/client.py,sha256=p-5z1iGM8ZydIrkYf4R6b7Yna73oszlGdXim9-Zsbyk,2364
5
5
  primitive/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- primitive/agent/actions.py,sha256=HuKR0bi3VVO8bR5IgcZ1paZw0lGK__8FTgfbWTm68go,5830
6
+ primitive/agent/actions.py,sha256=B7d2oNMjtjUP2RhD-QnNDWNl3jHwjUDk5KLWQ2OnNQ4,5883
7
7
  primitive/agent/commands.py,sha256=-dVDilELfkGfbZB7qfEPs77Dm1oT62qJj4tsIk4KoxI,254
8
- primitive/agent/process.py,sha256=2ZY3YoJHvoukrsCAZIt-AF2YKY4HEO5_jWji5K3W9fM,2267
9
- primitive/agent/provision.py,sha256=3EEzOV-ria6zf-pvfNddad1lzzd1QmfKInTIjmwX71Y,1673
10
- primitive/agent/runner.py,sha256=La1XSjH1vVj75DNI2rLR8GSRJvk3BosG_h_x1evGLCE,6878
11
- primitive/agent/uploader.py,sha256=5ZxonvRlMGRVBYx3hPEaWiPio5lMPi_zhtxz64V5S-A,2379
8
+ primitive/agent/process.py,sha256=LVI-RB4a0YEuXUTYMXKL5Xi9euNwUI2nxj00mv8EFOg,2253
9
+ primitive/agent/provision.py,sha256=rmwnro1K5F8mwtd45XAq7RVQmpDWnbBCQ8X_qgWhm3M,1546
10
+ primitive/agent/runner.py,sha256=pX1KgYNxSxkSIDydsQa60C4zV2EvNaVXA9rQlG17wc8,7129
11
+ primitive/agent/uploader.py,sha256=La33T2csENWKRsNagog3ELYe6110jufMnb7owmrPsIk,3150
12
12
  primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  primitive/auth/actions.py,sha256=MPsG9LcKcOPwA7gZ9Ewk0PZJhTQvIrGfODdz4GxSzgA,999
14
14
  primitive/auth/commands.py,sha256=JahUq0E2e7Xa-FX1WEUv7TgM6ieDvNH4VwRRtxAW7HE,2340
@@ -80,16 +80,16 @@ primitive/sim/commands.py,sha256=8PaOfL1MO6qxTn7mNVRnBU1X2wa3gk_mlbAhBW6MnI0,591
80
80
  primitive/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
82
82
  primitive/utils/auth.py,sha256=TtJKTR6tLmNrtWbOjJI-KJh4ZSJ1uG7ApE9GcY63m00,836
83
- primitive/utils/cache.py,sha256=hDVpEL2TePrWOH6q7Me_Oi-DH_viFrRxrVta-z4wBhM,1295
83
+ primitive/utils/cache.py,sha256=FHGmVWYLJFQOazpXXcEwI0YJEZbdkgG39nOLdOv6VNk,1575
84
84
  primitive/utils/config.py,sha256=DlFM5Nglo22WPtbpZSVtH7NX-PTMaKYlcrUE7GPRG4c,1058
85
85
  primitive/utils/files.py,sha256=Yv__bQes3YIlzhOT9kVxtYhoA5CmUjPSvphl9PZ41k4,867
86
86
  primitive/utils/git.py,sha256=1qNOu8X-33CavmrD580BmrFhD_WVO9PGWHUUboXJR_g,663
87
87
  primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
88
88
  primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
89
- primitive/utils/shell.py,sha256=-7UjQaBqSGHzEEyX8pNjeYFFP0P3lVnDV0OkgPz1qHU,1050
89
+ primitive/utils/shell.py,sha256=j7E1YwgNWw57dFHVfEbqRNVcPHX0xDefX2vFSNgeI_8,1648
90
90
  primitive/utils/verible.py,sha256=r7c_hfqvL0UicMmIzK3Cy_BfZI1ZpcfBeLqKEWFWqJo,2252
91
- primitive-0.1.59.dist-info/METADATA,sha256=pkPSFAV49Rz8KgwQyXJX9sjkuSlyc_mzsC_owgi7cto,3782
92
- primitive-0.1.59.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
93
- primitive-0.1.59.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
94
- primitive-0.1.59.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
95
- primitive-0.1.59.dist-info/RECORD,,
91
+ primitive-0.1.60.dist-info/METADATA,sha256=Ehe8DxemJilg_9uewSTTCQE9vobsu-P4EmSIKexXNLo,3782
92
+ primitive-0.1.60.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
93
+ primitive-0.1.60.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
94
+ primitive-0.1.60.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
95
+ primitive-0.1.60.dist-info/RECORD,,