primitive 0.1.52__tar.gz → 0.1.54__tar.gz

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 (63) hide show
  1. {primitive-0.1.52 → primitive-0.1.54}/PKG-INFO +1 -1
  2. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/__about__.py +1 -1
  3. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/agent/actions.py +3 -2
  4. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/agent/process.py +27 -32
  5. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/agent/runner.py +21 -15
  6. primitive-0.1.54/src/primitive/agent/uploader.py +61 -0
  7. primitive-0.1.52/src/primitive/agent/uploader.py +0 -46
  8. {primitive-0.1.52 → primitive-0.1.54}/.git-hooks/pre-commit +0 -0
  9. {primitive-0.1.52 → primitive-0.1.54}/.gitattributes +0 -0
  10. {primitive-0.1.52 → primitive-0.1.54}/.github/workflows/lint.yml +0 -0
  11. {primitive-0.1.52 → primitive-0.1.54}/.github/workflows/publish.yml +0 -0
  12. {primitive-0.1.52 → primitive-0.1.54}/.gitignore +0 -0
  13. {primitive-0.1.52 → primitive-0.1.54}/.vscode/settings.json +0 -0
  14. {primitive-0.1.52 → primitive-0.1.54}/LICENSE.txt +0 -0
  15. {primitive-0.1.52 → primitive-0.1.54}/Makefile +0 -0
  16. {primitive-0.1.52 → primitive-0.1.54}/README.md +0 -0
  17. {primitive-0.1.52 → primitive-0.1.54}/linux setup.md +0 -0
  18. {primitive-0.1.52 → primitive-0.1.54}/pyproject.toml +0 -0
  19. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/__init__.py +0 -0
  20. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/agent/commands.py +0 -0
  21. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/agent/provision.py +0 -0
  22. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/auth/__init__.py +0 -0
  23. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/auth/actions.py +0 -0
  24. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/auth/commands.py +0 -0
  25. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/cli.py +0 -0
  26. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/client.py +0 -0
  27. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/daemons/actions.py +0 -0
  28. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/daemons/commands.py +0 -0
  29. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/daemons/launch_agents.py +0 -0
  30. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/daemons/launch_service.py +0 -0
  31. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/files/actions.py +0 -0
  32. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/files/commands.py +0 -0
  33. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/git/__init__.py +0 -0
  34. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/git/actions.py +0 -0
  35. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/git/commands.py +0 -0
  36. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/graphql/__init__.py +0 -0
  37. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/graphql/sdk.py +0 -0
  38. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/hardware/actions.py +0 -0
  39. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/hardware/commands.py +0 -0
  40. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/jobs/actions.py +0 -0
  41. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/jobs/commands.py +0 -0
  42. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/lint/actions.py +0 -0
  43. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/lint/commands.py +0 -0
  44. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/organizations/actions.py +0 -0
  45. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/organizations/commands.py +0 -0
  46. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/projects/__init__.py +0 -0
  47. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/projects/actions.py +0 -0
  48. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/projects/commands.py +0 -0
  49. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/sim/__init__.py +0 -0
  50. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/sim/actions.py +0 -0
  51. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/sim/commands.py +0 -0
  52. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/actions.py +0 -0
  53. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/auth.py +0 -0
  54. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/cache.py +0 -0
  55. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/config.py +0 -0
  56. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/files.py +0 -0
  57. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/git.py +0 -0
  58. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/memory_size.py +0 -0
  59. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/printer.py +0 -0
  60. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/shell.py +0 -0
  61. {primitive-0.1.52 → primitive-0.1.54}/src/primitive/utils/verible.py +0 -0
  62. {primitive-0.1.52 → primitive-0.1.54}/tests/__init__.py +0 -0
  63. {primitive-0.1.52 → primitive-0.1.54}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: primitive
3
- Version: 0.1.52
3
+ Version: 0.1.54
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
1
  # SPDX-FileCopyrightText: 2024-present Dylan Stein <dylan@primitive.tech>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.52"
4
+ __version__ = "0.1.54"
@@ -1,4 +1,5 @@
1
1
  import sys
2
+ import shutil
2
3
  from time import sleep
3
4
  from primitive.utils.actions import BaseAction
4
5
  from loguru import logger
@@ -119,7 +120,7 @@ class Agent(BaseAction):
119
120
  )
120
121
 
121
122
  runner = AgentRunner(
122
- self.primitive,
123
+ primitive=self.primitive,
123
124
  source_dir=source_dir,
124
125
  job_id=job_run["id"],
125
126
  job_slug=job_run["job"]["slug"],
@@ -129,7 +130,7 @@ class Agent(BaseAction):
129
130
  runner.execute()
130
131
 
131
132
  # Clean up
132
- # shutil.rmtree(path=downloaded_git_repository_dir)
133
+ shutil.rmtree(path=downloaded_git_repository_dir)
133
134
 
134
135
  sleep(5)
135
136
  except KeyboardInterrupt:
@@ -1,6 +1,6 @@
1
1
  from subprocess import Popen, PIPE
2
- import threading
3
2
  import shlex
3
+ import selectors
4
4
  from loguru import logger
5
5
 
6
6
 
@@ -21,8 +21,7 @@ class Process:
21
21
 
22
22
  def start(self):
23
23
  # Start the process
24
- logger.info(f"cmd: {self.cmd}")
25
- logger.info(f"workdir: {self.workdir}")
24
+ self.sel = selectors.DefaultSelector()
26
25
  self.process = Popen(
27
26
  self.cmd,
28
27
  env=self.env,
@@ -32,40 +31,33 @@ class Process:
32
31
  text=True,
33
32
  )
34
33
 
35
- # Function to read and log output from a pipe
36
- def log_output(pipe, level):
37
- for line in iter(pipe.readline, ""):
38
- if line:
39
- logger.log(level, line.rstrip())
40
- if level == "ERROR":
41
- self._errors += 1
34
+ self.sel.register(self.process.stdout, selectors.EVENT_READ)
35
+ self.sel.register(self.process.stderr, selectors.EVENT_READ)
42
36
 
43
- pipe.close()
37
+ def probe_logs(self):
38
+ for key, _ in self.sel.select():
39
+ data = key.fileobj.readline()
40
+ if not data:
41
+ continue
44
42
 
45
- # Create threads for stdout and stderr
46
- self.stdout_thread = threading.Thread(
47
- target=log_output, args=(self.process.stdout, "INFO")
48
- )
49
- self.stderr_thread = threading.Thread(
50
- target=log_output, args=(self.process.stderr, "ERROR")
51
- )
52
-
53
- # Start the threads
54
- self.stdout_thread.start()
55
- self.stderr_thread.start()
43
+ if key.fileobj is self.process.stdout:
44
+ logger.info(data.rstrip())
45
+ elif key.fileobj is self.process.stderr:
46
+ logger.error(data.rstrip())
47
+ self._errors += 1
56
48
 
57
49
  def wait(self):
58
- if self.process:
59
- # Wait for the process to complete
60
- self.process.wait()
61
- # Wait for the threads to finish reading output
62
- self.stdout_thread.join()
63
- self.stderr_thread.join()
50
+ while True:
51
+ self.probe_logs()
52
+ if not self.is_running():
53
+ break
54
+
55
+ return self.finish()
64
56
 
65
57
  def run(self):
66
58
  """Start and wait for the process."""
67
59
  self.start()
68
- self.wait()
60
+ return self.wait()
69
61
 
70
62
  def is_running(self):
71
63
  """Check if the process is still running."""
@@ -73,10 +65,13 @@ class Process:
73
65
 
74
66
  def finish(self):
75
67
  """Make sure that logging finishes"""
76
- self.stderr_thread.join()
77
- self.stderr_thread.join()
68
+ if self.process:
69
+ self.sel.unregister(self.process.stdout)
70
+ self.sel.unregister(self.process.stderr)
71
+ self.process.stdout.close()
72
+ self.process.stderr.close()
78
73
 
79
- return self.process.poll()
74
+ return self.process.poll()
80
75
 
81
76
  def terminate(self):
82
77
  """Terminate the process."""
@@ -2,6 +2,7 @@ import yaml
2
2
  import sys
3
3
  import typing
4
4
  import os
5
+ import threading
5
6
  from time import sleep
6
7
  from typing import TypedDict, Iterable, List, Optional, Dict
7
8
  from pathlib import Path, PurePath
@@ -111,10 +112,8 @@ class AgentRunner:
111
112
  logger.info(f"Beginning step {step['name']}")
112
113
 
113
114
  # Define step proc
114
- logger.info(f"first cmd: {step['cmd']}")
115
- logger.info(f"first workdir: {Path(self.source_dir / step['workdir'])}")
116
115
  proc = Process(
117
- step["cmd"],
116
+ cmd=step["cmd"],
118
117
  workdir=Path(self.source_dir / step["workdir"]),
119
118
  env=environment,
120
119
  )
@@ -127,24 +126,31 @@ class AgentRunner:
127
126
  conclusion = "failure"
128
127
  break
129
128
 
130
- # Check for updates to status while running
131
- while proc.is_running():
132
- status = self.primitive.jobs.get_job_status(self.job_id)
129
+ def status_check():
130
+ while proc.is_running():
131
+ # Check job status
132
+ status = self.primitive.jobs.get_job_status(self.job_id)
133
+ status_value = status["jobRun"]["status"]
133
134
 
134
- status_value = status["jobRun"]["status"]
135
+ # TODO: Should probably use request_cancelled or something
136
+ # once we change it, we'll have to call conclude w/ cancelled status
137
+ if status_value == "completed":
138
+ logger.warning("Job cancelled by user")
139
+ proc.terminate()
140
+ return
135
141
 
136
- # TODO: Should probably use request_cancelled or something
137
- # once we change it, we'll have to call conclude w/ cancelled status
138
- if status_value == "completed":
139
- logger.warning("Job cancelled by user")
140
- proc.terminate()
141
- return
142
+ sleep(5)
142
143
 
143
- sleep(5)
144
+ status_thread = threading.Thread(target=status_check)
145
+ status_thread.start()
144
146
 
145
- returncode = proc.finish()
147
+ # Wait for proc to finish
148
+ returncode = proc.wait()
146
149
  total_errors += proc.errors
147
150
 
151
+ # Wait for status check
152
+ status_thread.join()
153
+
148
154
  # Collect artifacts
149
155
  if "artifacts" in step:
150
156
  self.collect_artifacts(step)
@@ -0,0 +1,61 @@
1
+ import typing
2
+ import shutil
3
+ from loguru import logger
4
+ from pathlib import Path, PurePath
5
+ from ..utils.cache import get_artifacts_cache
6
+
7
+ if typing.TYPE_CHECKING:
8
+ import primitive.client
9
+
10
+
11
+ class Uploader:
12
+ def __init__(
13
+ self,
14
+ primitive: "primitive.client.Primitive",
15
+ ):
16
+ self.primitive = primitive
17
+
18
+ def upload_file(self, path: Path, prefix: str) -> str:
19
+ file_upload_response = self.primitive.files.file_upload(path, key_prefix=prefix)
20
+ return file_upload_response.json()["data"]["fileUpload"]["id"]
21
+
22
+ def scan(self) -> None:
23
+ # Scan artifacts directory
24
+ artifacts_dir = get_artifacts_cache()
25
+
26
+ subdirs = sorted(
27
+ [job_cache for job_cache in artifacts_dir.iterdir() if job_cache.is_dir()],
28
+ key=lambda p: p.stat().st_ctime,
29
+ )
30
+
31
+ for job_cache in subdirs:
32
+ job_run_id = job_cache.name
33
+
34
+ files = sorted(
35
+ [
36
+ w_path / file
37
+ for w_path, _, w_files in job_cache.walk()
38
+ for file in w_files
39
+ ],
40
+ key=lambda p: p.stat().st_size,
41
+ )
42
+
43
+ file_ids = []
44
+ for file in files:
45
+ upload_id = self.upload_file(
46
+ file,
47
+ prefix=str(PurePath(file).relative_to(job_cache.parent).parent),
48
+ )
49
+
50
+ if upload_id:
51
+ file_ids.append(upload_id)
52
+ continue
53
+
54
+ logger.error(f"Unable to upload file {file}")
55
+
56
+ # Update job run
57
+ if len(file_ids) > 0:
58
+ self.primitive.jobs.job_run_update(id=job_run_id, file_ids=file_ids)
59
+
60
+ # Clean up job cache
61
+ shutil.rmtree(path=job_cache)
@@ -1,46 +0,0 @@
1
- import typing
2
- import shutil
3
- from pathlib import Path, PurePath
4
- from ..utils.cache import get_artifacts_cache
5
-
6
- if typing.TYPE_CHECKING:
7
- import primitive.client
8
-
9
-
10
- class Uploader:
11
- def __init__(
12
- self,
13
- primitive: "primitive.client.Primitive",
14
- ):
15
- self.primitive = primitive
16
-
17
- def upload_file(self, path: Path, prefix: str) -> str:
18
- file_upload_response = self.primitive.files.file_upload(path, key_prefix=prefix)
19
- return file_upload_response.json()["data"]["fileUpload"]["id"]
20
-
21
- def scan(self) -> None:
22
- # Scan artifacts directory
23
- artifacts_dir = get_artifacts_cache()
24
-
25
- subdirs = [
26
- job_cache for job_cache in artifacts_dir.iterdir() if job_cache.is_dir()
27
- ]
28
-
29
- for job_cache in subdirs:
30
- job_run_id = job_cache.name
31
- files = [file for file in job_cache.rglob("*") if file.is_file()]
32
-
33
- file_ids = []
34
- for file in files:
35
- file_ids.append(
36
- self.upload_file(
37
- file,
38
- prefix=str(PurePath(file).relative_to(job_cache.parent).parent),
39
- )
40
- )
41
-
42
- # Update job run
43
- self.primitive.jobs.job_run_update(id=job_run_id, file_ids=file_ids)
44
-
45
- # Clean up job cache
46
- shutil.rmtree(path=job_cache)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes