primitive 0.1.13__py3-none-any.whl → 0.1.15__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@steins.studio>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.13"
4
+ __version__ = "0.1.15"
@@ -32,7 +32,7 @@ class Agent(BaseAction):
32
32
  sleep(5)
33
33
  continue
34
34
 
35
- job_runs_data = self.primitive.projects.get_job_runs(
35
+ job_runs_data = self.primitive.jobs.get_job_runs(
36
36
  status="pending", first=1, reservation_id=active_reservation_id
37
37
  )
38
38
 
@@ -52,7 +52,7 @@ class Agent(BaseAction):
52
52
  )
53
53
 
54
54
  github_access_token = (
55
- self.primitive.projects.github_access_token_for_job_run(
55
+ self.primitive.jobs.github_access_token_for_job_run(
56
56
  job_run["id"]
57
57
  )
58
58
  )
@@ -65,28 +65,59 @@ class Agent(BaseAction):
65
65
  )
66
66
  )
67
67
 
68
- if job_run["job"]["slug"] == "lint":
69
- logger.debug("Executing Lint Job")
70
-
71
- self.primitive.projects.job_run_update(
72
- job_run["id"], status="request_in_progress"
73
- )
74
-
75
- result, message = self.primitive.lint.execute(
76
- source=downloaded_git_repository_dir
77
- )
78
- if result:
79
- conclusion = "success"
80
- else:
81
- conclusion = "failure"
82
- self.primitive.projects.job_run_update(
83
- job_run["id"],
84
- status="request_completed",
85
- conclusion=conclusion,
86
- stdout=message,
87
- )
88
-
89
- logger.debug("Lint Job Completed")
68
+ match job_run["job"]["slug"]:
69
+ case "lint":
70
+ logger.debug("Executing Lint Job")
71
+
72
+ self.primitive.jobs.job_run_update(
73
+ job_run["id"], status="request_in_progress"
74
+ )
75
+
76
+ result, message = self.primitive.lint.execute(
77
+ source=downloaded_git_repository_dir
78
+ )
79
+ if result:
80
+ conclusion = "success"
81
+ else:
82
+ conclusion = "failure"
83
+ self.primitive.jobs.job_run_update(
84
+ job_run["id"],
85
+ status="request_completed",
86
+ conclusion=conclusion,
87
+ stdout=message,
88
+ )
89
+
90
+ logger.debug("Lint Job Completed")
91
+ case "sim":
92
+ logger.debug("Executing Sim Job")
93
+
94
+ self.primitive.job.job_run_update(
95
+ job_run["id"], status="request_in_progress"
96
+ )
97
+
98
+ result, message = self.primitive.sim.execute(
99
+ source=downloaded_git_repository_dir,
100
+ cmd=(
101
+ "make",
102
+ "all",
103
+ ), # TODO: Change this to use container args container cmd
104
+ )
105
+
106
+ # Attempt artifact collection
107
+ self.primitive.sim.collect_artifacts(
108
+ source=downloaded_git_repository_dir
109
+ )
110
+
111
+ if result:
112
+ conclusion = "success"
113
+ else:
114
+ conclusion = "failure"
115
+ self.primitive.jobs.job_run_update(
116
+ job_run["id"],
117
+ status="request_completed",
118
+ conclusion=conclusion,
119
+ stdout=message,
120
+ )
90
121
 
91
122
  sleep(5)
92
123
  except KeyboardInterrupt:
primitive/cli.py CHANGED
@@ -10,6 +10,10 @@ from .lint.commands import cli as lint_commands
10
10
  from .agent.commands import cli as agent_commands
11
11
  from .git.commands import cli as git_commands
12
12
  from .daemons.commands import cli as daemons_commands
13
+ from .jobs.commands import cli as jobs_commands
14
+ from .organizations.commands import cli as organizations_commands
15
+ from .projects.commands import cli as projects_commands
16
+ from .sim.commands import cli as sim_commands
13
17
 
14
18
 
15
19
  @click.group()
@@ -61,6 +65,10 @@ cli.add_command(lint_commands, "lint")
61
65
  cli.add_command(agent_commands, "agent")
62
66
  cli.add_command(git_commands, "git")
63
67
  cli.add_command(daemons_commands, "daemons")
68
+ cli.add_command(jobs_commands, "jobs")
69
+ cli.add_command(organizations_commands, "organizations")
70
+ cli.add_command(projects_commands, "projects")
71
+ cli.add_command(sim_commands, "sim")
64
72
 
65
73
  if __name__ == "__main__":
66
74
  cli(obj={})
primitive/client.py CHANGED
@@ -4,12 +4,14 @@ from .projects.actions import Projects
4
4
  from .graphql.sdk import create_session
5
5
  from .utils.config import read_config_file
6
6
  from .files.actions import Files
7
- from .simulations.actions import Simulations
7
+ from .sim.actions import Sim
8
8
  from .hardware.actions import Hardware
9
9
  from .lint.actions import Lint
10
10
  from .agent.actions import Agent
11
11
  from .git.actions import Git
12
12
  from .daemons.actions import Daemons
13
+ from .jobs.actions import Jobs
14
+ from .organizations.actions import Organizations
13
15
 
14
16
  from loguru import logger
15
17
 
@@ -55,9 +57,11 @@ class Primitive:
55
57
  )
56
58
 
57
59
  self.auth: Auth = Auth(self)
60
+ self.organizations: Organizations = Organizations(self)
58
61
  self.projects: Projects = Projects(self)
62
+ self.jobs: Jobs = Jobs(self)
59
63
  self.files: Files = Files(self)
60
- self.simulations: Simulations = Simulations(self)
64
+ self.sim: Sim = Sim(self)
61
65
  self.hardware: Hardware = Hardware(self)
62
66
  self.lint: Lint = Lint(self)
63
67
  self.agent: Agent = Agent(self)
@@ -6,6 +6,7 @@ HOME_DIRECTORY = Path.home()
6
6
  CURRENT_USER = str(HOME_DIRECTORY.expanduser()).lstrip("/Users/")
7
7
 
8
8
  PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
9
+
9
10
  PRIMITIVE_LAUNCH_AGENT_FILEPATH = Path(
10
11
  HOME_DIRECTORY / "Library" / "LaunchAgents" / "tech.primitive.agent.plist"
11
12
  )
@@ -78,6 +79,15 @@ def populate_fresh_launch_agent():
78
79
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
79
80
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.touch()
80
81
 
82
+ found_primitive_binary_path = PRIMITIVE_BINARY_PATH
83
+ if not PRIMITIVE_BINARY_PATH.exists():
84
+ result = subprocess.run(["which", "primitive"], capture_output=True)
85
+ if result.returncode == 0:
86
+ found_primitive_binary_path = result.stdout.decode().rstrip("\n")
87
+ else:
88
+ print("primitive binary not found")
89
+ return False
90
+
81
91
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.write_text(
82
92
  f"""<?xml version="1.0" encoding="UTF-8"?>
83
93
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -96,7 +106,7 @@ def populate_fresh_launch_agent():
96
106
  </array>
97
107
  <key>ProgramArguments</key>
98
108
  <array>
99
- <string>{PRIMITIVE_BINARY_PATH}</string>
109
+ <string>{found_primitive_binary_path}</string>
100
110
  <string>agent</string>
101
111
  </array>
102
112
  <key>RunAtLoad</key>
@@ -82,8 +82,17 @@ def populate_service_file():
82
82
  "After": "network.target",
83
83
  }
84
84
 
85
+ found_primitive_binary_path = PRIMITIVE_BINARY_PATH
86
+ if not PRIMITIVE_BINARY_PATH.exists():
87
+ result = subprocess.run(["which", "primitive"], capture_output=True)
88
+ if result.returncode == 0:
89
+ found_primitive_binary_path = result.stdout.decode().rstrip("\n")
90
+ else:
91
+ print("primitive binary not found")
92
+ return False
93
+
85
94
  config["Service"] = {
86
- "ExecStart": f"{PRIMITIVE_BINARY_PATH} agent",
95
+ "ExecStart": f"{found_primitive_binary_path} agent",
87
96
  "Restart": "always",
88
97
  "StandardError": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
89
98
  "StandardOutput": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
@@ -0,0 +1,307 @@
1
+ from typing import List, Optional
2
+ from gql import gql
3
+
4
+
5
+ from primitive.utils.actions import BaseAction
6
+
7
+
8
+ class Jobs(BaseAction):
9
+ def get_jobs(
10
+ self,
11
+ organization_id: Optional[str] = None,
12
+ project_id: Optional[str] = None,
13
+ job_id: Optional[str] = None,
14
+ slug: Optional[str] = None,
15
+ first: Optional[int] = 1,
16
+ last: Optional[int] = None,
17
+ ):
18
+ query = gql(
19
+ """
20
+ fragment PageInfoFragment on PageInfo {
21
+ hasNextPage
22
+ hasPreviousPage
23
+ startCursor
24
+ endCursor
25
+ }
26
+
27
+ fragment JobFragment on Job {
28
+ id
29
+ pk
30
+ slug
31
+ name
32
+ createdAt
33
+ updatedAt
34
+ }
35
+
36
+ query jobs(
37
+ $before: String
38
+ $after: String
39
+ $first: Int
40
+ $last: Int
41
+ $filters: JobFilters
42
+ ) {
43
+ jobs(
44
+ before: $before
45
+ after: $after
46
+ first: $first
47
+ last: $last
48
+ filters: $filters
49
+ ) {
50
+ totalCount
51
+ pageInfo {
52
+ ...PageInfoFragment
53
+ }
54
+ edges {
55
+ cursor
56
+ node {
57
+ ...JobFragment
58
+ }
59
+ }
60
+ }
61
+ }
62
+ """
63
+ )
64
+
65
+ filters = {}
66
+ if organization_id:
67
+ filters["organization"] = {"id": organization_id}
68
+ if project_id:
69
+ filters["project"] = {"id": project_id}
70
+ if job_id:
71
+ filters["id"] = job_id
72
+ if slug:
73
+ filters["slug"] = {"exact": slug}
74
+
75
+ variables = {
76
+ "first": first,
77
+ "last": last,
78
+ "filters": filters,
79
+ "order": {
80
+ "createdAt": "DESC",
81
+ },
82
+ }
83
+
84
+ result = self.primitive.session.execute(
85
+ query, variable_values=variables, get_execution_result=True
86
+ )
87
+ jobs = [edge["node"] for edge in result.data["jobs"]["edges"]]
88
+ return jobs
89
+
90
+ def get_job_runs(
91
+ self,
92
+ organization_id: Optional[str] = None,
93
+ project_id: Optional[str] = None,
94
+ job_id: Optional[str] = None,
95
+ reservation_id: Optional[str] = None,
96
+ git_commit_id: Optional[str] = None,
97
+ status: Optional[str] = None,
98
+ conclusion: Optional[str] = None,
99
+ first: Optional[int] = 1,
100
+ last: Optional[int] = None,
101
+ ):
102
+ query = gql(
103
+ """
104
+ fragment PageInfoFragment on PageInfo {
105
+ hasNextPage
106
+ hasPreviousPage
107
+ startCursor
108
+ endCursor
109
+ }
110
+
111
+ fragment JobRunFragment on JobRun {
112
+ id
113
+ pk
114
+ createdAt
115
+ updatedAt
116
+ completedAt
117
+ startedAt
118
+ status
119
+ conclusion
120
+ stdout
121
+ job {
122
+ id
123
+ pk
124
+ slug
125
+ name
126
+ createdAt
127
+ updatedAt
128
+ }
129
+ jobSettings {
130
+ containerArgs
131
+ containerCommand
132
+ }
133
+ gitCommit {
134
+ sha
135
+ branch
136
+ repoFullName
137
+ }
138
+ }
139
+
140
+ query jobRuns(
141
+ $before: String
142
+ $after: String
143
+ $first: Int
144
+ $last: Int
145
+ $filters: JobRunFilters
146
+ $order: JobRunOrder
147
+ ) {
148
+ jobRuns(
149
+ before: $before
150
+ after: $after
151
+ first: $first
152
+ last: $last
153
+ filters: $filters
154
+ order: $order
155
+ ) {
156
+ totalCount
157
+ pageInfo {
158
+ ...PageInfoFragment
159
+ }
160
+ edges {
161
+ cursor
162
+ node {
163
+ ...JobRunFragment
164
+ }
165
+ }
166
+ }
167
+ }
168
+ """
169
+ )
170
+
171
+ filters = {}
172
+ if organization_id:
173
+ filters["organization"] = {"id": organization_id}
174
+ if project_id:
175
+ filters["project"] = {"id": project_id}
176
+ if job_id:
177
+ filters["job"] = {"id": job_id}
178
+ if reservation_id:
179
+ filters["reservation"] = {"id": reservation_id}
180
+ if git_commit_id:
181
+ filters["gitCommit"] = {"id": git_commit_id}
182
+ if status:
183
+ filters["status"] = {"exact": status}
184
+ if conclusion:
185
+ filters["conclusion"] = {"exact": status}
186
+
187
+ variables = {
188
+ "first": first,
189
+ "last": last,
190
+ "filters": filters,
191
+ "order": {
192
+ "createdAt": "DESC",
193
+ },
194
+ }
195
+
196
+ result = self.primitive.session.execute(query, variable_values=variables)
197
+ return result
198
+
199
+ def get_job_run(self, id: str):
200
+ query = gql(
201
+ """
202
+ fragment JobRunFragment on JobRun {
203
+ id
204
+ pk
205
+ createdAt
206
+ updatedAt
207
+ completedAt
208
+ startedAt
209
+ status
210
+ conclusion
211
+ stdout
212
+ job {
213
+ id
214
+ pk
215
+ slug
216
+ name
217
+ createdAt
218
+ updatedAt
219
+ }
220
+ gitCommit {
221
+ sha
222
+ branch
223
+ repoFullName
224
+ }
225
+ }
226
+
227
+ query jobRun($id: GlobalID!) {
228
+ jobRun(id: $id) {
229
+ ...JobRunFragment
230
+ }
231
+ }
232
+ """
233
+ )
234
+ variables = {"id": id}
235
+ result = self.primitive.session.execute(query, variable_values=variables)
236
+ return result
237
+
238
+ def job_run_update(
239
+ self,
240
+ id: str,
241
+ status: str = None,
242
+ conclusion: str = None,
243
+ stdout: str = None,
244
+ file_ids: Optional[List[str]] = [],
245
+ ):
246
+ mutation = gql(
247
+ """
248
+ mutation jobRunUpdate($input: JobRunUpdateInput!) {
249
+ jobRunUpdate(input: $input) {
250
+ ... on JobRun {
251
+ id
252
+ status
253
+ conclusion
254
+ }
255
+ }
256
+ }
257
+ """
258
+ )
259
+ input = {"id": id}
260
+ if status:
261
+ input["status"] = status
262
+ if conclusion:
263
+ input["conclusion"] = conclusion
264
+ if file_ids and len(file_ids) > 0:
265
+ input["files"] = file_ids
266
+ if stdout:
267
+ input["stdout"] = stdout
268
+ variables = {"input": input}
269
+ result = self.primitive.session.execute(mutation, variable_values=variables)
270
+ return result
271
+
272
+ def github_access_token_for_job_run(self, job_run_id: str):
273
+ query = gql(
274
+ """
275
+ query ghAppTokenForJobRun($jobRunId: GlobalID!) {
276
+ ghAppTokenForJobRun(jobRunId: $jobRunId)
277
+ }
278
+ """
279
+ )
280
+ variables = {"jobRunId": job_run_id}
281
+ result = self.primitive.session.execute(query, variable_values=variables)
282
+ return result["ghAppTokenForJobRun"]
283
+
284
+ def get_latest_job_run_for_job(
285
+ self, job_slug: Optional[str] = None, job_id: Optional[str] = None
286
+ ):
287
+ if not job_slug and not job_id:
288
+ raise ValueError("job_slug or job_id is required")
289
+ jobs_results = self.get_jobs(slug=job_slug)
290
+ jobs = [edge["node"] for edge in jobs_results.data["jobs"]["edges"]]
291
+
292
+ job_id = jobs.id
293
+ job_run_results = self.get_job_runs(job_id=job_id, first=1)
294
+ job_run = [edge["node"] for edge in job_run_results.data["job_runs"]["edges"]][
295
+ 0
296
+ ]
297
+ return job_run
298
+
299
+ def job_run_start(
300
+ self,
301
+ job_slug: str,
302
+ job_id: str,
303
+ ):
304
+ if not job_slug and not job_id:
305
+ raise ValueError("job_slug or job_id is required")
306
+
307
+ self.get_jobs(slug=job_slug)
@@ -0,0 +1,41 @@
1
+ import click
2
+
3
+ from ..utils.printer import print_result
4
+
5
+ import typing
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from ..client import Primitive
9
+
10
+
11
+ @click.group()
12
+ @click.pass_context
13
+ def cli(context):
14
+ """Jobs Commands"""
15
+ pass
16
+
17
+
18
+ @cli.command("list")
19
+ @click.pass_context
20
+ def list(
21
+ context,
22
+ organization_slug: str = None,
23
+ organization_id: str = None,
24
+ ):
25
+ """List Job"""
26
+ primitive: Primitive = context.obj.get("PRIMITIVE")
27
+ message = primitive.jobs.get_jobs()
28
+ print_result(message=message, context=context)
29
+
30
+
31
+ @cli.command("details")
32
+ @click.argument("job_slug")
33
+ @click.pass_context
34
+ def details(
35
+ context,
36
+ job_slug: str = None,
37
+ ):
38
+ """List Job"""
39
+ primitive: Primitive = context.obj.get("PRIMITIVE")
40
+ message = primitive.jobs.get_jobs(slug=job_slug)
41
+ print_result(message=message, context=context)
primitive/lint/actions.py CHANGED
@@ -9,7 +9,7 @@ from ..utils.verible import install_verible
9
9
 
10
10
  class Lint(BaseAction):
11
11
  def execute(self, source: Path = Path.cwd()) -> Tuple[bool, str]:
12
- logger.debug("Starting linter for source: {source}")
12
+ logger.debug(f"Starting linter for source: {source}")
13
13
  files = find_files_for_extension(source, ".sv")
14
14
  if not files:
15
15
  message = "No files found to lint."
@@ -0,0 +1,84 @@
1
+ from typing import Optional
2
+ from gql import gql
3
+
4
+
5
+ from primitive.utils.actions import BaseAction
6
+
7
+
8
+ class Organizations(BaseAction):
9
+ def get_organizations(
10
+ self,
11
+ organization_id: Optional[str] = None,
12
+ slug: Optional[str] = None,
13
+ first: Optional[int] = 1,
14
+ last: Optional[int] = None,
15
+ ):
16
+ query = gql(
17
+ """
18
+ fragment PageInfoFragment on PageInfo {
19
+ hasNextPage
20
+ hasPreviousPage
21
+ startCursor
22
+ endCursor
23
+ }
24
+
25
+ fragment OrganizationFragment on Organization {
26
+ id
27
+ pk
28
+ slug
29
+ name
30
+ createdAt
31
+ updatedAt
32
+ }
33
+
34
+ query organizations(
35
+ $before: String
36
+ $after: String
37
+ $first: Int
38
+ $last: Int
39
+ $filters: OrganizationFilters
40
+ $order: OrganizationOrder
41
+ ) {
42
+ organizations(
43
+ before: $before
44
+ after: $after
45
+ first: $first
46
+ last: $last
47
+ filters: $filters
48
+ order: $order
49
+ ) {
50
+ totalCount
51
+ pageInfo {
52
+ ...PageInfoFragment
53
+ }
54
+ edges {
55
+ cursor
56
+ node {
57
+ ...OrganizationFragment
58
+ }
59
+ }
60
+ }
61
+ }
62
+ """
63
+ )
64
+
65
+ filters = {}
66
+ if organization_id:
67
+ filters["organization"] = {"id": organization_id}
68
+ if slug:
69
+ filters["slug"] = {"exact": slug}
70
+
71
+ variables = {
72
+ "first": first,
73
+ "last": last,
74
+ "filters": filters,
75
+ "order": {
76
+ "createdAt": "DESC",
77
+ },
78
+ }
79
+
80
+ result = self.primitive.session.execute(
81
+ query, variable_values=variables, get_execution_result=True
82
+ )
83
+ organizations = [edge["node"] for edge in result.data["organizations"]["edges"]]
84
+ return organizations
@@ -0,0 +1,24 @@
1
+ import click
2
+
3
+ from ..utils.printer import print_result
4
+
5
+ import typing
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from ..client import Primitive
9
+
10
+
11
+ @click.group()
12
+ @click.pass_context
13
+ def cli(context):
14
+ """Organizations Commands"""
15
+ pass
16
+
17
+
18
+ @cli.command("list")
19
+ @click.pass_context
20
+ def details(context):
21
+ """List Organizations"""
22
+ primitive: Primitive = context.obj.get("PRIMITIVE")
23
+ message = primitive.organizations.get_organizations()
24
+ print_result(message=message, context=context)