primitive 0.1.12__py3-none-any.whl → 0.1.14__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.12"
4
+ __version__ = "0.1.14"
@@ -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
  )
@@ -68,7 +68,7 @@ class Agent(BaseAction):
68
68
  if job_run["job"]["slug"] == "lint":
69
69
  logger.debug("Executing Lint Job")
70
70
 
71
- self.primitive.projects.job_run_update(
71
+ self.primitive.jobs.job_run_update(
72
72
  job_run["id"], status="request_in_progress"
73
73
  )
74
74
 
@@ -79,7 +79,7 @@ class Agent(BaseAction):
79
79
  conclusion = "success"
80
80
  else:
81
81
  conclusion = "failure"
82
- self.primitive.projects.job_run_update(
82
+ self.primitive.jobs.job_run_update(
83
83
  job_run["id"],
84
84
  status="request_completed",
85
85
  conclusion=conclusion,
primitive/cli.py CHANGED
@@ -9,6 +9,10 @@ from .hardware.commands import cli as hardware_commands
9
9
  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
+ 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
12
16
 
13
17
 
14
18
  @click.group()
@@ -59,6 +63,10 @@ cli.add_command(hardware_commands, "hardware")
59
63
  cli.add_command(lint_commands, "lint")
60
64
  cli.add_command(agent_commands, "agent")
61
65
  cli.add_command(git_commands, "git")
66
+ cli.add_command(daemons_commands, "daemons")
67
+ cli.add_command(jobs_commands, "jobs")
68
+ cli.add_command(organizations_commands, "organizations")
69
+ cli.add_command(projects_commands, "projects")
62
70
 
63
71
  if __name__ == "__main__":
64
72
  cli(obj={})
primitive/client.py CHANGED
@@ -9,6 +9,9 @@ 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
+ from .daemons.actions import Daemons
13
+ from .jobs.actions import Jobs
14
+ from .organizations.actions import Organizations
12
15
 
13
16
  from loguru import logger
14
17
 
@@ -54,13 +57,16 @@ class Primitive:
54
57
  )
55
58
 
56
59
  self.auth: Auth = Auth(self)
60
+ self.organizations: Organizations = Organizations(self)
57
61
  self.projects: Projects = Projects(self)
62
+ self.jobs: Jobs = Jobs(self)
58
63
  self.files: Files = Files(self)
59
64
  self.simulations: Simulations = Simulations(self)
60
65
  self.hardware: Hardware = Hardware(self)
61
66
  self.lint: Lint = Lint(self)
62
67
  self.agent: Agent = Agent(self)
63
68
  self.git: Git = Git(self)
69
+ self.daemons: Daemons = Daemons(self)
64
70
 
65
71
  def get_host_config(self):
66
72
  self.full_config = read_config_file()
@@ -0,0 +1,75 @@
1
+ import platform
2
+ import typing
3
+
4
+ if typing.TYPE_CHECKING:
5
+ from ..client import Primitive
6
+
7
+ from .launch_agents import (
8
+ full_launch_agent_install,
9
+ full_launch_agent_uninstall,
10
+ start_launch_agent,
11
+ stop_launch_agent,
12
+ view_launch_agent_logs,
13
+ )
14
+
15
+ from .launch_service import (
16
+ full_service_install,
17
+ full_service_uninstall,
18
+ start_service,
19
+ stop_service,
20
+ view_service_logs,
21
+ )
22
+
23
+
24
+ class Daemons:
25
+ def __init__(self, primitive) -> None:
26
+ self.primitive: Primitive = primitive
27
+ self.os_family = platform.system()
28
+
29
+ def install(self):
30
+ result = True
31
+ if self.os_family == "Darwin":
32
+ full_launch_agent_install()
33
+ elif self.os_family == "Linux":
34
+ full_service_install()
35
+ elif self.os_family == "Windows":
36
+ print("Not Implemented")
37
+ return result
38
+
39
+ def uninstall(self):
40
+ result = True
41
+ if self.os_family == "Darwin":
42
+ full_launch_agent_uninstall()
43
+ elif self.os_family == "Linux":
44
+ full_service_uninstall()
45
+ elif self.os_family == "Windows":
46
+ print("Not Implemented")
47
+ return result
48
+
49
+ def stop(self) -> bool:
50
+ result = True
51
+ if self.os_family == "Darwin":
52
+ result = stop_launch_agent()
53
+ elif self.os_family == "Linux":
54
+ stop_service()
55
+ elif self.os_family == "Windows":
56
+ print("Not Implemented")
57
+ return result
58
+
59
+ def start(self) -> bool:
60
+ result = True
61
+ if self.os_family == "Darwin":
62
+ result = start_launch_agent()
63
+ elif self.os_family == "Linux":
64
+ start_service()
65
+ elif self.os_family == "Windows":
66
+ print("Not Implemented")
67
+ return result
68
+
69
+ def logs(self):
70
+ if self.os_family == "Darwin":
71
+ view_launch_agent_logs()
72
+ elif self.os_family == "Linux":
73
+ view_service_logs()
74
+ elif self.os_family == "Windows":
75
+ print("Not Implemented")
@@ -0,0 +1,65 @@
1
+ import click
2
+
3
+ from ..utils.printer import print_result
4
+ import typing
5
+
6
+ if typing.TYPE_CHECKING:
7
+ from ..client import Primitive
8
+
9
+
10
+ @click.group()
11
+ @click.pass_context
12
+ def cli(context):
13
+ """Daemon"""
14
+ pass
15
+
16
+
17
+ @cli.command("install")
18
+ @click.pass_context
19
+ def install_daemon_command(context):
20
+ """Install the full primitive daemon"""
21
+ primitive: Primitive = context.obj.get("PRIMITIVE")
22
+ result = primitive.daemons.install()
23
+ print_result(message=result, context=context)
24
+
25
+
26
+ @cli.command("uninstall")
27
+ @click.pass_context
28
+ def uninstall_daemon_command(context):
29
+ """Uninstall the full primitive Daemon"""
30
+ primitive: Primitive = context.obj.get("PRIMITIVE")
31
+ result = primitive.daemons.uninstall()
32
+ print_result(message=result, context=context)
33
+
34
+
35
+ @cli.command("stop")
36
+ @click.pass_context
37
+ def stop_daemon_command(context):
38
+ """Stop primitive Daemon"""
39
+ primitive: Primitive = context.obj.get("PRIMITIVE")
40
+ result = primitive.daemons.stop()
41
+ message = "stopping primitive daemon"
42
+ if context.obj["JSON"]:
43
+ message = result
44
+ print_result(message=message, context=context)
45
+
46
+
47
+ @cli.command("start")
48
+ @click.pass_context
49
+ def start_daemon_command(context):
50
+ """Start primitive Daemon"""
51
+ primitive: Primitive = context.obj.get("PRIMITIVE")
52
+ result = primitive.daemons.start()
53
+ message = "starting primitive daemon"
54
+ if context.obj["JSON"]:
55
+ message = result
56
+ print_result(message=message, context=context)
57
+
58
+
59
+ @cli.command("logs")
60
+ @click.pass_context
61
+ def log_daemon_command(context):
62
+ """Logs from primitive Daemon"""
63
+ primitive: Primitive = context.obj.get("PRIMITIVE")
64
+ result = primitive.daemons.logs()
65
+ print_result(message=result, context=context)
@@ -0,0 +1,144 @@
1
+ import os
2
+ from pathlib import Path
3
+ import subprocess
4
+
5
+ HOME_DIRECTORY = Path.home()
6
+ CURRENT_USER = str(HOME_DIRECTORY.expanduser()).lstrip("/Users/")
7
+
8
+ PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
9
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH = Path(
10
+ HOME_DIRECTORY / "Library" / "LaunchAgents" / "tech.primitive.agent.plist"
11
+ )
12
+ PRIMITIVE_LAUNCH_AGENT_LOGS = Path(
13
+ HOME_DIRECTORY / "Library" / "Logs" / "tech.primitive.agent.log"
14
+ )
15
+ PRIMITIVE_LAUNCH_AGENT_LABEL = "tech.primitive.agent"
16
+ PRIMITIVE_LAUNCH_AGENT_WORKING_DIR = Path(
17
+ HOME_DIRECTORY / "Logs" / "tech.primitive.agent.log"
18
+ )
19
+
20
+
21
+ def stop_launch_agent():
22
+ try:
23
+ stop_existing_process = f"launchctl stop {PRIMITIVE_LAUNCH_AGENT_LABEL}"
24
+ subprocess.check_output(stop_existing_process.split(" "))
25
+ return True
26
+ except subprocess.CalledProcessError as exception:
27
+ print("stop_launch_agent: ", exception)
28
+ return False
29
+
30
+
31
+ def start_launch_agent():
32
+ try:
33
+ start_new_agent = f"launchctl start {PRIMITIVE_LAUNCH_AGENT_LABEL}"
34
+ subprocess.check_output(start_new_agent.split(" "))
35
+ return True
36
+ except subprocess.CalledProcessError as exception:
37
+ print("start_launch_agent: ", exception)
38
+ return False
39
+
40
+
41
+ def unload_launch_agent():
42
+ try:
43
+ remove_existing_agent = f"launchctl unload -w {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
44
+ subprocess.check_output(remove_existing_agent.split(" "))
45
+ return True
46
+ except subprocess.CalledProcessError as exception:
47
+ print("remove_launch_agent: ", exception)
48
+ return False
49
+
50
+
51
+ def load_launch_agent():
52
+ try:
53
+ load_new_plist = f"launchctl load -w {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
54
+ subprocess.check_output(load_new_plist.split(" "))
55
+ return True
56
+ except subprocess.CalledProcessError as exception:
57
+ print("load_launch_agent: ", exception)
58
+ return False
59
+
60
+
61
+ def create_stdout_file():
62
+ if not PRIMITIVE_LAUNCH_AGENT_LOGS.exists():
63
+ PRIMITIVE_LAUNCH_AGENT_LOGS.parent.mkdir(parents=True, exist_ok=True)
64
+ PRIMITIVE_LAUNCH_AGENT_LOGS.touch()
65
+
66
+
67
+ def delete_stdout_file():
68
+ if PRIMITIVE_LAUNCH_AGENT_LOGS.exists():
69
+ PRIMITIVE_LAUNCH_AGENT_LOGS.unlink()
70
+
71
+
72
+ def populate_fresh_launch_agent():
73
+ PRIMITIVE_LAUNCH_AGENT_LOGS.parent.mkdir(parents=True, exist_ok=True)
74
+ PRIMITIVE_LAUNCH_AGENT_LOGS.touch()
75
+
76
+ if PRIMITIVE_LAUNCH_AGENT_FILEPATH.exists():
77
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.unlink()
78
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
79
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.touch()
80
+
81
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.write_text(
82
+ f"""<?xml version="1.0" encoding="UTF-8"?>
83
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
84
+ <plist version="1.0">
85
+ <dict>
86
+ <key>KeepAlive</key>
87
+ <true/>
88
+ <key>Label</key>
89
+ <string>{PRIMITIVE_LAUNCH_AGENT_LABEL}</string>
90
+ <key>LimitLoadToSessionType</key>
91
+ <array>
92
+ <string>Aqua</string>
93
+ <string>Background</string>
94
+ <string>LoginWindow</string>
95
+ <string>StandardIO</string>
96
+ </array>
97
+ <key>ProgramArguments</key>
98
+ <array>
99
+ <string>{PRIMITIVE_BINARY_PATH}</string>
100
+ <string>agent</string>
101
+ </array>
102
+ <key>RunAtLoad</key>
103
+ <true/>
104
+ <key>StandardErrorPath</key>
105
+ <string>{PRIMITIVE_LAUNCH_AGENT_LOGS}</string>
106
+ <key>StandardOutPath</key>
107
+ <string>{PRIMITIVE_LAUNCH_AGENT_LOGS}</string>
108
+ </dict>
109
+ </plist>""" # noqa: E501
110
+ )
111
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.chmod(0o644)
112
+ verify_launch_agent()
113
+
114
+
115
+ def verify_launch_agent():
116
+ plutil_check = f"plutil -lint {PRIMITIVE_LAUNCH_AGENT_FILEPATH}"
117
+ try:
118
+ subprocess.check_output(plutil_check.split(" "))
119
+ return True
120
+ except subprocess.CalledProcessError as exception:
121
+ print("verify_launch_agent: ", exception)
122
+ return False
123
+
124
+
125
+ def view_launch_agent_logs():
126
+ follow_logs = f"tail -f -n +1 {PRIMITIVE_LAUNCH_AGENT_LOGS}"
127
+ os.system(follow_logs)
128
+
129
+
130
+ def full_launch_agent_install():
131
+ stop_launch_agent()
132
+ unload_launch_agent()
133
+ populate_fresh_launch_agent()
134
+ create_stdout_file()
135
+ load_launch_agent()
136
+ start_launch_agent()
137
+
138
+
139
+ def full_launch_agent_uninstall():
140
+ stop_launch_agent()
141
+ unload_launch_agent()
142
+ if PRIMITIVE_LAUNCH_AGENT_FILEPATH.exists():
143
+ PRIMITIVE_LAUNCH_AGENT_FILEPATH.unlink()
144
+ delete_stdout_file()
@@ -0,0 +1,170 @@
1
+ import os
2
+ import configparser
3
+ import subprocess
4
+ from pathlib import Path
5
+
6
+ HOME_DIRECTORY = Path.home()
7
+
8
+ PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
9
+
10
+ PRIMITIVE_AGENT_SERVICE = "tech.primitive.agent.service"
11
+ PRIMITIVE_AGENT_LOGS = "tech.primitive.agent.log"
12
+ PRIMITIVE_AGENT_SERVICE_FILEPATH = Path(
13
+ HOME_DIRECTORY / ".config" / "systemd" / "user" / PRIMITIVE_AGENT_SERVICE
14
+ )
15
+ PRIMITIVE_AGENT_LOGS_FILEPATH = Path(
16
+ HOME_DIRECTORY / ".cache" / "primitive" / PRIMITIVE_AGENT_LOGS
17
+ )
18
+
19
+
20
+ def stop_service():
21
+ try:
22
+ if is_service_active():
23
+ stop_existing_service = f"systemctl --user stop {PRIMITIVE_AGENT_SERVICE}"
24
+ subprocess.check_output(stop_existing_service.split(" "))
25
+ return True
26
+ except subprocess.CalledProcessError as exception:
27
+ print("stop_service: ", exception)
28
+ return False
29
+
30
+
31
+ def is_service_active():
32
+ try:
33
+ is_service_active = (
34
+ f"systemctl --user show {PRIMITIVE_AGENT_SERVICE} -p ActiveState --value"
35
+ )
36
+ output = subprocess.check_output(is_service_active.split(" ")).decode().strip()
37
+ return output == "active"
38
+ except subprocess.CalledProcessError as exception:
39
+ print("is_service_active: ", exception)
40
+ return False
41
+
42
+
43
+ def disable_service():
44
+ try:
45
+ if is_service_enabled():
46
+ disable_existing_service = (
47
+ f"systemctl --user disable {PRIMITIVE_AGENT_SERVICE}"
48
+ )
49
+ subprocess.check_output(disable_existing_service.split(" "))
50
+ return True
51
+ except subprocess.CalledProcessError as exception:
52
+ print("disable_service: ", exception)
53
+ return False
54
+
55
+
56
+ def is_service_enabled():
57
+ try:
58
+ is_service_active = (
59
+ f"systemctl --user show {PRIMITIVE_AGENT_SERVICE} -p UnitFileState --value" # noqa
60
+ )
61
+ output = subprocess.check_output(is_service_active.split(" ")).decode().strip()
62
+ return output == "enabled"
63
+ except subprocess.CalledProcessError as exception:
64
+ print("is_service_enabled: ", exception)
65
+ return False
66
+
67
+
68
+ def populate_service_file():
69
+ PRIMITIVE_AGENT_LOGS_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
70
+ PRIMITIVE_AGENT_LOGS_FILEPATH.touch()
71
+
72
+ if PRIMITIVE_AGENT_SERVICE_FILEPATH.exists():
73
+ PRIMITIVE_AGENT_SERVICE_FILEPATH.unlink()
74
+ PRIMITIVE_AGENT_SERVICE_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
75
+ PRIMITIVE_AGENT_SERVICE_FILEPATH.touch()
76
+
77
+ config = configparser.ConfigParser()
78
+ config.optionxform = str # type: ignore
79
+
80
+ config["Unit"] = {
81
+ "Description": "Primitive Agent",
82
+ "After": "network.target",
83
+ }
84
+
85
+ config["Service"] = {
86
+ "ExecStart": f"{PRIMITIVE_BINARY_PATH} agent",
87
+ "Restart": "always",
88
+ "StandardError": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
89
+ "StandardOutput": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
90
+ }
91
+
92
+ config["Install"] = {
93
+ "WantedBy": "default.target",
94
+ }
95
+
96
+ try:
97
+ with open(PRIMITIVE_AGENT_SERVICE_FILEPATH, "w") as service_file:
98
+ config.write(service_file)
99
+ except IOError as exception:
100
+ print(f"populate_service_file: {exception}")
101
+
102
+ PRIMITIVE_AGENT_SERVICE_FILEPATH.chmod(0o644)
103
+ verify_service_file()
104
+
105
+
106
+ def verify_service_file():
107
+ systemctl_check = (
108
+ f"systemctl --user show {PRIMITIVE_AGENT_SERVICE} -p CanStart --value"
109
+ )
110
+ try:
111
+ output = subprocess.check_output(systemctl_check.split(" ")).decode().strip()
112
+ if output == "no":
113
+ raise Exception(f"{systemctl_check} yielded {output}")
114
+ return True
115
+ except subprocess.CalledProcessError as exception:
116
+ print("verify_service_file: ", exception)
117
+ return False
118
+
119
+
120
+ def create_stdout_file():
121
+ if not PRIMITIVE_AGENT_LOGS_FILEPATH.exists():
122
+ PRIMITIVE_AGENT_LOGS_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
123
+ PRIMITIVE_AGENT_LOGS_FILEPATH.touch()
124
+
125
+
126
+ def delete_stdout_file():
127
+ if PRIMITIVE_AGENT_LOGS_FILEPATH.exists():
128
+ PRIMITIVE_AGENT_LOGS_FILEPATH.unlink()
129
+
130
+
131
+ def enable_service():
132
+ try:
133
+ enable_service = f"systemctl --user enable {PRIMITIVE_AGENT_SERVICE}"
134
+ subprocess.check_output(enable_service.split(" "))
135
+ return True
136
+ except subprocess.CalledProcessError as exception:
137
+ print("enable_service: ", exception)
138
+ return False
139
+
140
+
141
+ def start_service():
142
+ try:
143
+ start_new_service = f"systemctl --user start {PRIMITIVE_AGENT_SERVICE}"
144
+ subprocess.check_output(start_new_service.split(" "))
145
+ return True
146
+ except subprocess.CalledProcessError as exception:
147
+ print("start_service: ", exception)
148
+ return False
149
+
150
+
151
+ def view_service_logs():
152
+ follow_logs = f"tail -f -n +1 {PRIMITIVE_AGENT_LOGS_FILEPATH}"
153
+ os.system(follow_logs)
154
+
155
+
156
+ def full_service_install():
157
+ stop_service()
158
+ disable_service()
159
+ populate_service_file()
160
+ create_stdout_file()
161
+ enable_service()
162
+ start_service()
163
+
164
+
165
+ def full_service_uninstall():
166
+ stop_service()
167
+ disable_service()
168
+ if PRIMITIVE_AGENT_SERVICE_FILEPATH.exists():
169
+ PRIMITIVE_AGENT_SERVICE_FILEPATH.unlink()
170
+ delete_stdout_file()
@@ -0,0 +1,303 @@
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
+ gitCommit {
130
+ sha
131
+ branch
132
+ repoFullName
133
+ }
134
+ }
135
+
136
+ query jobRuns(
137
+ $before: String
138
+ $after: String
139
+ $first: Int
140
+ $last: Int
141
+ $filters: JobRunFilters
142
+ $order: JobRunOrder
143
+ ) {
144
+ jobRuns(
145
+ before: $before
146
+ after: $after
147
+ first: $first
148
+ last: $last
149
+ filters: $filters
150
+ order: $order
151
+ ) {
152
+ totalCount
153
+ pageInfo {
154
+ ...PageInfoFragment
155
+ }
156
+ edges {
157
+ cursor
158
+ node {
159
+ ...JobRunFragment
160
+ }
161
+ }
162
+ }
163
+ }
164
+ """
165
+ )
166
+
167
+ filters = {}
168
+ if organization_id:
169
+ filters["organization"] = {"id": organization_id}
170
+ if project_id:
171
+ filters["project"] = {"id": project_id}
172
+ if job_id:
173
+ filters["job"] = {"id": job_id}
174
+ if reservation_id:
175
+ filters["reservation"] = {"id": reservation_id}
176
+ if git_commit_id:
177
+ filters["gitCommit"] = {"id": git_commit_id}
178
+ if status:
179
+ filters["status"] = {"exact": status}
180
+ if conclusion:
181
+ filters["conclusion"] = {"exact": status}
182
+
183
+ variables = {
184
+ "first": first,
185
+ "last": last,
186
+ "filters": filters,
187
+ "order": {
188
+ "createdAt": "DESC",
189
+ },
190
+ }
191
+
192
+ result = self.primitive.session.execute(query, variable_values=variables)
193
+ return result
194
+
195
+ def get_job_run(self, id: str):
196
+ query = gql(
197
+ """
198
+ fragment JobRunFragment on JobRun {
199
+ id
200
+ pk
201
+ createdAt
202
+ updatedAt
203
+ completedAt
204
+ startedAt
205
+ status
206
+ conclusion
207
+ stdout
208
+ job {
209
+ id
210
+ pk
211
+ slug
212
+ name
213
+ createdAt
214
+ updatedAt
215
+ }
216
+ gitCommit {
217
+ sha
218
+ branch
219
+ repoFullName
220
+ }
221
+ }
222
+
223
+ query jobRun($id: GlobalID!) {
224
+ jobRun(id: $id) {
225
+ ...JobRunFragment
226
+ }
227
+ }
228
+ """
229
+ )
230
+ variables = {"id": id}
231
+ result = self.primitive.session.execute(query, variable_values=variables)
232
+ return result
233
+
234
+ def job_run_update(
235
+ self,
236
+ id: str,
237
+ status: str = None,
238
+ conclusion: str = None,
239
+ stdout: str = None,
240
+ file_ids: Optional[List[str]] = [],
241
+ ):
242
+ mutation = gql(
243
+ """
244
+ mutation jobRunUpdate($input: JobRunUpdateInput!) {
245
+ jobRunUpdate(input: $input) {
246
+ ... on JobRun {
247
+ id
248
+ status
249
+ conclusion
250
+ }
251
+ }
252
+ }
253
+ """
254
+ )
255
+ input = {"id": id}
256
+ if status:
257
+ input["status"] = status
258
+ if conclusion:
259
+ input["conclusion"] = conclusion
260
+ if file_ids and len(file_ids) > 0:
261
+ input["files"] = file_ids
262
+ if stdout:
263
+ input["stdout"] = stdout
264
+ variables = {"input": input}
265
+ result = self.primitive.session.execute(mutation, variable_values=variables)
266
+ return result
267
+
268
+ def github_access_token_for_job_run(self, job_run_id: str):
269
+ query = gql(
270
+ """
271
+ query ghAppTokenForJobRun($jobRunId: GlobalID!) {
272
+ ghAppTokenForJobRun(jobRunId: $jobRunId)
273
+ }
274
+ """
275
+ )
276
+ variables = {"jobRunId": job_run_id}
277
+ result = self.primitive.session.execute(query, variable_values=variables)
278
+ return result["ghAppTokenForJobRun"]
279
+
280
+ def get_latest_job_run_for_job(
281
+ self, job_slug: Optional[str] = None, job_id: Optional[str] = None
282
+ ):
283
+ if not job_slug and not job_id:
284
+ raise ValueError("job_slug or job_id is required")
285
+ jobs_results = self.get_jobs(slug=job_slug)
286
+ jobs = [edge["node"] for edge in jobs_results.data["jobs"]["edges"]]
287
+
288
+ job_id = jobs.id
289
+ job_run_results = self.get_job_runs(job_id=job_id, first=1)
290
+ job_run = [edge["node"] for edge in job_run_results.data["job_runs"]["edges"]][
291
+ 0
292
+ ]
293
+ return job_run
294
+
295
+ def job_run_start(
296
+ self,
297
+ job_slug: str,
298
+ job_id: str,
299
+ ):
300
+ if not job_slug and not job_id:
301
+ raise ValueError("job_slug or job_id is required")
302
+
303
+ 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)
@@ -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)
@@ -1,175 +1,79 @@
1
- from typing import List, Optional
2
1
  from gql import gql
3
2
 
4
-
3
+ from typing import Optional
5
4
  from primitive.utils.actions import BaseAction
6
5
 
7
6
 
8
7
  class Projects(BaseAction):
9
- def get_job_runs(
8
+
9
+ def get_projects(
10
10
  self,
11
11
  organization_id: Optional[str] = None,
12
- project_id: Optional[str] = None,
13
- job_id: Optional[str] = None,
14
- reservation_id: Optional[str] = None,
15
- git_commit_id: Optional[str] = None,
16
- status: Optional[str] = None,
17
- conclusion: Optional[str] = None,
12
+ slug: Optional[str] = None,
18
13
  first: Optional[int] = 1,
19
14
  last: Optional[int] = None,
20
15
  ):
21
16
  query = gql(
22
- """
23
- fragment PageInfoFragment on PageInfo {
24
- hasNextPage
25
- hasPreviousPage
26
- startCursor
27
- endCursor
28
- }
17
+ """
18
+ fragment PageInfoFragment on PageInfo {
19
+ hasNextPage
20
+ hasPreviousPage
21
+ startCursor
22
+ endCursor
23
+ }
29
24
 
30
- fragment JobRunFragment on JobRun {
31
- id
32
- pk
33
- createdAt
34
- updatedAt
35
- completedAt
36
- startedAt
37
- status
38
- conclusion
39
- stdout
40
- job {
41
- id
42
- pk
43
- slug
44
- name
45
- createdAt
46
- updatedAt
47
- }
48
- gitCommit {
49
- sha
50
- branch
51
- repoFullName
52
- }
53
- }
25
+ fragment ProjectFragment on Project {
26
+ id
27
+ pk
28
+ slug
29
+ name
30
+ createdAt
31
+ updatedAt
32
+ }
54
33
 
55
- query jobRuns(
56
- $before: String
57
- $after: String
58
- $first: Int
59
- $last: Int
60
- $filters: JobRunFilters
61
- $order: JobRunOrder
62
- ) {
63
- jobRuns(
64
- before: $before
65
- after: $after
66
- first: $first
67
- last: $last
68
- filters: $filters
69
- order: $order
70
- ) {
71
- totalCount
72
- pageInfo {
73
- ...PageInfoFragment
74
- }
75
- edges {
76
- cursor
77
- node {
78
- ...JobRunFragment
79
- }
80
- }
81
- }
82
- }
83
- """
34
+ query projects(
35
+ $before: String
36
+ $after: String
37
+ $first: Int
38
+ $last: Int
39
+ $filters: ProjectFilters
40
+ ) {
41
+ projects(
42
+ before: $before
43
+ after: $after
44
+ first: $first
45
+ last: $last
46
+ filters: $filters
47
+ ) {
48
+ totalCount
49
+ pageInfo {
50
+ ...PageInfoFragment
51
+ }
52
+ edges {
53
+ cursor
54
+ node {
55
+ ...ProjectFragment
56
+ }
57
+ }
58
+ }
59
+ }
60
+ """
84
61
  )
85
62
 
86
63
  filters = {}
87
64
  if organization_id:
88
65
  filters["organization"] = {"id": organization_id}
89
- if project_id:
90
- filters["project"] = {"id": project_id}
91
- if job_id:
92
- filters["job"] = {"id": job_id}
93
- if reservation_id:
94
- filters["reservation"] = {"id": reservation_id}
95
- if git_commit_id:
96
- filters["gitCommit"] = {"id": git_commit_id}
97
- if status:
98
- filters["status"] = {"exact": status}
99
- if conclusion:
100
- filters["conclusion"] = {"exact": status}
66
+ if slug:
67
+ filters["slug"] = {"exact": slug}
101
68
 
102
69
  variables = {
103
70
  "first": first,
104
71
  "last": last,
105
72
  "filters": filters,
106
- "order": {
107
- "createdAt": "DESC",
108
- },
109
73
  }
110
74
 
111
- result = self.primitive.session.execute(query, variable_values=variables)
112
- return result
113
-
114
- def get_job_run(self, id: str):
115
- query = gql(
116
- """
117
- query jobRun($id: GlobalID!) {
118
- jobRun(id: $id) {
119
- id
120
- organization {
121
- id
122
- }
123
- }
124
- }
125
- """
126
- )
127
- variables = {"id": id}
128
- result = self.primitive.session.execute(query, variable_values=variables)
129
- return result
130
-
131
- def job_run_update(
132
- self,
133
- id: str,
134
- status: str = None,
135
- conclusion: str = None,
136
- stdout: str = None,
137
- file_ids: Optional[List[str]] = [],
138
- ):
139
- mutation = gql(
140
- """
141
- mutation jobRunUpdate($input: JobRunUpdateInput!) {
142
- jobRunUpdate(input: $input) {
143
- ... on JobRun {
144
- id
145
- status
146
- conclusion
147
- }
148
- }
149
- }
150
- """
151
- )
152
- input = {"id": id}
153
- if status:
154
- input["status"] = status
155
- if conclusion:
156
- input["conclusion"] = conclusion
157
- if file_ids and len(file_ids) > 0:
158
- input["files"] = file_ids
159
- if stdout:
160
- input["stdout"] = stdout
161
- variables = {"input": input}
162
- result = self.primitive.session.execute(mutation, variable_values=variables)
163
- return result
164
-
165
- def github_access_token_for_job_run(self, job_run_id: str):
166
- query = gql(
167
- """
168
- query ghAppTokenForJobRun($jobRunId: GlobalID!) {
169
- ghAppTokenForJobRun(jobRunId: $jobRunId)
170
- }
171
- """
75
+ result = self.primitive.session.execute(
76
+ query, variable_values=variables, get_execution_result=True
172
77
  )
173
- variables = {"jobRunId": job_run_id}
174
- result = self.primitive.session.execute(query, variable_values=variables)
175
- return result["ghAppTokenForJobRun"]
78
+ projects = [edge["node"] for edge in result.data["projects"]["edges"]]
79
+ return projects
@@ -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
+ """Projects Commands"""
15
+ pass
16
+
17
+
18
+ @cli.command("list")
19
+ @click.pass_context
20
+ def list(context):
21
+ """List Projects"""
22
+ primitive: Primitive = context.obj.get("PRIMITIVE")
23
+ message = primitive.projects.get_projects()
24
+ print_result(message=message, context=context)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: primitive
3
- Version: 0.1.12
3
+ Version: 0.1.14
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,12 +1,16 @@
1
- primitive/__about__.py,sha256=35zrg1jAPnEjmmKrCnH1TpiDYB4jwmc5kkbIoAHlG84,129
1
+ primitive/__about__.py,sha256=NZWfnlUO354pWRTdV9CUZL2iipJ1paYR9Gljq6eTciM,129
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
- primitive/cli.py,sha256=fWv1gnUs7U6yWRrv1fYjt2bNBclIvp6okKTxrl_Il24,1879
4
- primitive/client.py,sha256=dkbHg2Pbxl7NjIk2O6Shvz7TE7uDQezGQkNwmTzKdNs,2146
5
- primitive/agent/actions.py,sha256=8eQDvUy0THT-zVvSTVgrDAsthRdSIpmhCkDr0iTXqDE,3673
3
+ primitive/cli.py,sha256=VA1c9RiH79mqYvIvYT0msu4tEPlYiU1C2LstbdzyjWk,2291
4
+ primitive/client.py,sha256=FYc-Onu1c0fjttSWlhK0B1ZOizDsP3qGtOAUHvPGqG0,2410
5
+ primitive/agent/actions.py,sha256=8G9-QjUKRvlvlZ_6NyZr2KvvW1FTS54q-KPUzyAPccE,3657
6
6
  primitive/agent/commands.py,sha256=-dVDilELfkGfbZB7qfEPs77Dm1oT62qJj4tsIk4KoxI,254
7
7
  primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  primitive/auth/actions.py,sha256=N2bGcwXNsB89pzs66gF9A5_WzUScY5fhfOyWixqo2y8,1054
9
9
  primitive/auth/commands.py,sha256=JahUq0E2e7Xa-FX1WEUv7TgM6ieDvNH4VwRRtxAW7HE,2340
10
+ primitive/daemons/actions.py,sha256=Nt3yNtbBhen0jK4sRsH_N7AP3UBuyL48VaUhtC7wYq8,2015
11
+ primitive/daemons/commands.py,sha256=-Muh-6ib4uAVtPn_67AcMrDwuCwYlCnRQozCi2Xurmk,1726
12
+ primitive/daemons/launch_agents.py,sha256=xR_JDL93mt5-fSbj-v3qvYFvLqfn486fPNbNxZOb1wU,4412
13
+ primitive/daemons/launch_service.py,sha256=3ktr0n0rSNA-IV6hQ8hbG50L2Y3V8-IeW79C8CrZi8U,5203
10
14
  primitive/files/actions.py,sha256=f4JN3QFB2WXw-0JpnE-4-movnqtvXIpCrGd_9pdkeW4,2624
11
15
  primitive/files/commands.py,sha256=DDizo3xJnU3KLUBTMeeM72viVpnJinLwxs84tmqKhqo,810
12
16
  primitive/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -16,10 +20,15 @@ primitive/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
16
20
  primitive/graphql/sdk.py,sha256=BhCGmDtc4sNnH8CxbQSJyFwOZ-ZSqMtjsxMB3JRBhPw,1456
17
21
  primitive/hardware/actions.py,sha256=Ea3_2E3F_3WapV60g_mOIcpXhadoknwihR7slXyUWtk,18840
18
22
  primitive/hardware/commands.py,sha256=QE7LLeFdfOqlvz3JwdwJJRZAY3fHI1zB9kYmmDajpq0,1477
23
+ primitive/jobs/actions.py,sha256=ftVWIB10PxTrFsDmSd9C3WV3puiQftTLBKFIjcS_StI,7562
24
+ primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
19
25
  primitive/lint/actions.py,sha256=PDw0fkIkI5hHHjoHaAiXvQUocwHZkgB2mfI-LGMl6TI,2267
20
26
  primitive/lint/commands.py,sha256=3CZvkOEMpJspJWmaQzA5bpPKx0_VCijQIXA9l-eTnZE,487
27
+ primitive/organizations/actions.py,sha256=e0V4E1UK1IcBJsWWH6alHYUmArhzPrBqZ8WkHPIcLq0,2268
28
+ primitive/organizations/commands.py,sha256=_dwgVEJCqMa5VgB_7P1wLPFc0AuT1p9dtyR9JRr4kpw,487
21
29
  primitive/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- primitive/projects/actions.py,sha256=4Fwq2KTBuhDy8SfEIqPhR_8rW2rAL76cGWIQS40a96A,4039
30
+ primitive/projects/actions.py,sha256=L2M2SvFdDr2XnpoxFl61jXmIriD4T1vVMoBFgCy6SY4,2062
31
+ primitive/projects/commands.py,sha256=Fqqgpi4cm6zOgkHK--0F0hiiIj32BmgZ-h1MydmWwdE,464
23
32
  primitive/simulations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
33
  primitive/simulations/actions.py,sha256=YR0oxxd7_kuUIH77BWZLUs9rLRiSJztPPhpgDJU2PbY,1267
25
34
  primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
@@ -30,8 +39,8 @@ primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIU
30
39
  primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
31
40
  primitive/utils/shell.py,sha256=-7UjQaBqSGHzEEyX8pNjeYFFP0P3lVnDV0OkgPz1qHU,1050
32
41
  primitive/utils/verible.py,sha256=QYczN1IvxODfj4jeq0nqjFuF0Oi0Zdx-Q32ySOJgcw8,2205
33
- primitive-0.1.12.dist-info/METADATA,sha256=Zpkvn5C7K77B5vWuLDBp1SATMc_sZM6iR4J15DVzN8w,1818
34
- primitive-0.1.12.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
35
- primitive-0.1.12.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
36
- primitive-0.1.12.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
37
- primitive-0.1.12.dist-info/RECORD,,
42
+ primitive-0.1.14.dist-info/METADATA,sha256=L7Npr3zJ77e2VpLmfKrhJ7bb3OuI2LKLxIS7Nvak7SQ,1818
43
+ primitive-0.1.14.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
44
+ primitive-0.1.14.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
45
+ primitive-0.1.14.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
46
+ primitive-0.1.14.dist-info/RECORD,,