datatorch 0.6.2__tar.gz → 0.7.1__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.
- {datatorch-0.6.2 → datatorch-0.7.1}/PKG-INFO +2 -2
- {datatorch-0.6.2 → datatorch-0.7.1}/README.md +1 -1
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/__init__.py +11 -7
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/client.py +14 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/monitoring.py +27 -2
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/runner.py +11 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/api.py +15 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/client.py +5 -2
- datatorch-0.7.1/datatorch/api/entity/dataset.py +103 -0
- datatorch-0.7.1/datatorch/api/pull.py +584 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/scripts/import_coco.py +4 -4
- datatorch-0.7.1/datatorch/cli/agent/__init__.py +32 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/agent/create.py +8 -6
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/agent/dir.py +1 -1
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/agent/start.py +1 -1
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/groups.py +4 -1
- datatorch-0.7.1/datatorch/cli/main/pull.py +151 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/core/settings.py +4 -1
- datatorch-0.7.1/datatorch/utils/url.py +9 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/PKG-INFO +2 -2
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/SOURCES.txt +2 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/setup.py +1 -1
- datatorch-0.6.2/datatorch/api/entity/dataset.py +0 -48
- datatorch-0.6.2/datatorch/cli/agent/__init__.py +0 -15
- datatorch-0.6.2/datatorch/utils/url.py +0 -5
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/agent.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/directory.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/log_handler.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/logging.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/action/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/action/action.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/action/cache.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/action/config.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/job/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/job/job.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/pipeline.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/resolver.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/command.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/docker.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/factory.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/node.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/python.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/runner/shell.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/step/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/step/step.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/pipelines/template.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/services/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/utils/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/utils/docker.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/agent/utils/process.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/annotation.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/base.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/file.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/label.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/project.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/settings.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/image/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/image/bounding_box.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/image/segmentations.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/image/typings.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/sources/source.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/storage_link.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/entity/user.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/scripts/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/scripts/import_pixmask.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/scripts/utils/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/scripts/utils/simplify.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/utils.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/api/where.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/action/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/action/create.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/action/pull.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/action/run.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/import_cmds/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/import_cmds/coco.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/import_cmds/pixmask.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/main/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/main/login.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/main/logout.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/main/upgrade.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/main/version.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/pipeline/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/pipeline/generate.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/pipeline/run.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/pipeline/upload.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/spinner.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/upload/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/upload/folder.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/cli/utils.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/core/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/core/env.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/core/folder.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/__init__.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/converters.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/files.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/hash_table.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/objects.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/package.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch/utils/string_style.py +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/dependency_links.txt +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/entry_points.txt +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/not-zip-safe +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/requires.txt +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/datatorch.egg-info/top_level.txt +0 -0
- {datatorch-0.6.2 → datatorch-0.7.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datatorch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: A CLI and library for interacting with DataTorch.
|
|
5
5
|
Home-page: https://github.com/datatorch/python
|
|
6
6
|
Author: DataTorch
|
|
@@ -95,4 +95,4 @@ python3 -m venv env
|
|
|
95
95
|
|
|
96
96
|
rm -rf env
|
|
97
97
|
|
|
98
|
-
source env/bin/activate && pip3 install dist/datatorch-0.6.
|
|
98
|
+
source env/bin/activate && pip3 install dist/datatorch-0.6.3-py3-none-any.whl && pip3 install pycocotools
|
|
@@ -12,8 +12,7 @@ from .agent import Agent, tasks
|
|
|
12
12
|
from gql import Client as GqlClient
|
|
13
13
|
from gql.transport.exceptions import TransportClosed, TransportServerError
|
|
14
14
|
from gql.transport.websockets import WebsocketsTransport
|
|
15
|
-
from websockets.exceptions import InvalidMessage, InvalidURI
|
|
16
|
-
from websockets import ConnectionClosedError
|
|
15
|
+
from websockets.exceptions import ConnectionClosed, InvalidMessage, InvalidURI
|
|
17
16
|
|
|
18
17
|
from datatorch.api import Client as DtClient
|
|
19
18
|
from datatorch.utils.package import get_version
|
|
@@ -110,12 +109,17 @@ async def start() -> None:
|
|
|
110
109
|
await Agent.run(session)
|
|
111
110
|
|
|
112
111
|
except (
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
# ConnectionClosed covers both the error and the "OK" close
|
|
113
|
+
# (a server-side restart closes cleanly); OSError covers the
|
|
114
|
+
# socket-level failures seen right after a laptop wakes
|
|
115
|
+
# (DNS gaierror, ConnectionReset, ConnectionRefused, timeouts).
|
|
116
|
+
ConnectionClosed,
|
|
117
|
+
OSError,
|
|
115
118
|
IncompleteReadError,
|
|
116
119
|
TransportServerError,
|
|
117
120
|
TransportClosed,
|
|
118
121
|
InvalidMessage,
|
|
122
|
+
asyncio.TimeoutError,
|
|
119
123
|
) as e:
|
|
120
124
|
await _exit_jobs()
|
|
121
125
|
await _close_transport(transport)
|
|
@@ -139,12 +143,12 @@ async def stop() -> None:
|
|
|
139
143
|
|
|
140
144
|
print(" ")
|
|
141
145
|
|
|
142
|
-
logger.warning("Gracefully exiting
|
|
146
|
+
logger.warning("Gracefully exiting runner.")
|
|
143
147
|
|
|
144
|
-
logger.info("Closing
|
|
148
|
+
logger.info("Closing runner jobs.")
|
|
145
149
|
await _exit_jobs()
|
|
146
150
|
|
|
147
|
-
logger.info("Closing
|
|
151
|
+
logger.info("Closing runner jobs.")
|
|
148
152
|
await _exit_jobs()
|
|
149
153
|
|
|
150
154
|
logger.info("Closing all other tasks.")
|
|
@@ -4,6 +4,9 @@ from .directory import agent_directory
|
|
|
4
4
|
from gql.client import AsyncClientSession
|
|
5
5
|
|
|
6
6
|
from typing_extensions import TypedDict
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class Log(TypedDict):
|
|
@@ -236,6 +239,17 @@ class AgentApiClient(object):
|
|
|
236
239
|
# fmt: on
|
|
237
240
|
return self.execute(mutate, params={"id": step_id, "logs": logs})
|
|
238
241
|
|
|
242
|
+
async def force_reconnect(self) -> None:
|
|
243
|
+
"""Closes the websocket so the steps subscription raises and the
|
|
244
|
+
outer loop in `agent.start()` reconnects with backoff."""
|
|
245
|
+
transport = getattr(self.session, "transport", None)
|
|
246
|
+
if transport is None or getattr(transport, "websocket", None) is None:
|
|
247
|
+
return
|
|
248
|
+
try:
|
|
249
|
+
await transport.close()
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.debug(f"Transport close raised while forcing reconnect: {e}")
|
|
252
|
+
|
|
239
253
|
async def execute(self, query, *args, params: dict = {}, **kwargs) -> dict:
|
|
240
254
|
"""Wrapper around execute"""
|
|
241
255
|
removed_none = dict((k, v) for k, v in params.items() if v is not None)
|
|
@@ -67,10 +67,17 @@ class AgentSystemStats(object):
|
|
|
67
67
|
print_stats(stats)
|
|
68
68
|
return stats
|
|
69
69
|
|
|
70
|
+
# Consecutive failed reports before the connection is considered dead.
|
|
71
|
+
# The steps subscription can sit silently on a half-open socket (e.g.
|
|
72
|
+
# after sleep); the report failing is the signal that reaches us.
|
|
73
|
+
MAX_REPORT_FAILURES = 3
|
|
74
|
+
REPORT_TIMEOUT = 30
|
|
75
|
+
|
|
70
76
|
def __init__(self, agent, sample_rate=60):
|
|
71
77
|
self.agent = agent
|
|
72
78
|
self.sample_rate = sample_rate
|
|
73
79
|
self.sample = 0
|
|
80
|
+
self.report_failures = 0
|
|
74
81
|
|
|
75
82
|
async def start(self):
|
|
76
83
|
try:
|
|
@@ -90,7 +97,25 @@ class AgentSystemStats(object):
|
|
|
90
97
|
while True:
|
|
91
98
|
self.sample += 1
|
|
92
99
|
stats = self.stats()
|
|
93
|
-
loop = asyncio.get_running_loop()
|
|
94
100
|
if self.sample != 1:
|
|
95
|
-
|
|
101
|
+
await self._report(stats)
|
|
96
102
|
await asyncio.sleep(self.sample_rate)
|
|
103
|
+
|
|
104
|
+
async def _report(self, stats):
|
|
105
|
+
"""Sends one metric report; repeated failures force a reconnect."""
|
|
106
|
+
try:
|
|
107
|
+
await asyncio.wait_for(
|
|
108
|
+
self.agent.api.metrics(stats), timeout=self.REPORT_TIMEOUT
|
|
109
|
+
)
|
|
110
|
+
self.report_failures = 0
|
|
111
|
+
except asyncio.CancelledError:
|
|
112
|
+
raise
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.report_failures += 1
|
|
115
|
+
logger.warning(
|
|
116
|
+
f"Metric report failed ({self.report_failures}/{self.MAX_REPORT_FAILURES}): {e}"
|
|
117
|
+
)
|
|
118
|
+
if self.report_failures >= self.MAX_REPORT_FAILURES:
|
|
119
|
+
logger.error("Connection looks dead; forcing a reconnect.")
|
|
120
|
+
self.report_failures = 0
|
|
121
|
+
await self.agent.api.force_reconnect()
|
|
@@ -14,6 +14,12 @@ class ProcessCodeError(Exception):
|
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# Max size of one stdout line from an action subprocess. Output values
|
|
18
|
+
# arrive as one `::var::<json>` line, so this bounds the largest single
|
|
19
|
+
# output value (e.g. an http-request body), not ordinary log lines.
|
|
20
|
+
STDOUT_LINE_LIMIT = 16 * 1024 * 1024
|
|
21
|
+
|
|
22
|
+
|
|
17
23
|
class Runner(object):
|
|
18
24
|
def __init__(self, config: dict, action: "Action"):
|
|
19
25
|
self.config = config
|
|
@@ -110,6 +116,11 @@ class Runner(object):
|
|
|
110
116
|
stdout=asyncio.subprocess.PIPE,
|
|
111
117
|
stderr=asyncio.subprocess.STDOUT,
|
|
112
118
|
env={**os.environ, **(env or {})},
|
|
119
|
+
# Outputs are reported as single `::var::<json>` stdout lines
|
|
120
|
+
# (see check_for_output), so a line can carry an entire fetched
|
|
121
|
+
# page body — asyncio's 64KB default readline limit kills the
|
|
122
|
+
# stream with LimitOverrunError on anything larger.
|
|
123
|
+
limit=STDOUT_LINE_LIMIT,
|
|
113
124
|
)
|
|
114
125
|
if wait:
|
|
115
126
|
await process.wait()
|
|
@@ -56,6 +56,14 @@ _FILE = File.add_fragment("""
|
|
|
56
56
|
}
|
|
57
57
|
""")
|
|
58
58
|
|
|
59
|
+
_DATASET = Dataset.add_fragment("""
|
|
60
|
+
query GetDataset($id: ID!) {
|
|
61
|
+
dataset: datasetById(id: $id) {
|
|
62
|
+
...DatasetFields
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
""")
|
|
66
|
+
|
|
59
67
|
_VIEWER = User.add_fragment("""
|
|
60
68
|
query GetViewer {
|
|
61
69
|
viewer {
|
|
@@ -119,6 +127,13 @@ class ApiClient(Client):
|
|
|
119
127
|
File, self.query_to_class(File, _FILE, path="file", params={"fileId": id})
|
|
120
128
|
)
|
|
121
129
|
|
|
130
|
+
def dataset(self, id: str) -> Dataset:
|
|
131
|
+
"""Retrieve a dataset (or branch) by ID"""
|
|
132
|
+
return cast(
|
|
133
|
+
Dataset,
|
|
134
|
+
self.query_to_class(Dataset, _DATASET, path="dataset", params={"id": id}),
|
|
135
|
+
)
|
|
136
|
+
|
|
122
137
|
def upload_to_default_filesource(
|
|
123
138
|
self,
|
|
124
139
|
project: Project,
|
|
@@ -162,7 +162,8 @@ class Client(object):
|
|
|
162
162
|
dl_file = os.path.join(directory, name)
|
|
163
163
|
dl_file = os.path.abspath(dl_file)
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
# `isfile(x) & skip == True` only worked by bitwise-bool accident
|
|
166
|
+
if skip and os.path.isfile(dl_file):
|
|
166
167
|
return dl_file, None
|
|
167
168
|
|
|
168
169
|
query_string = urlencode({"download": "true", "stream": "true"})
|
|
@@ -172,8 +173,10 @@ class Client(object):
|
|
|
172
173
|
result = requests.get(
|
|
173
174
|
download_url, headers={self.token_header: self._api_token}, stream=True
|
|
174
175
|
)
|
|
176
|
+
# Never write an error body to disk as a "downloaded file"
|
|
177
|
+
result.raise_for_status()
|
|
175
178
|
|
|
176
|
-
content = result.headers
|
|
179
|
+
content = result.headers.get("content-disposition", "")
|
|
177
180
|
# _, value = cgi.parse_header(content)
|
|
178
181
|
parser = HeaderParser()
|
|
179
182
|
headers = parser.parsestr(f"Content-Disposition: {content}")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from .base import BaseEntity
|
|
2
|
+
|
|
3
|
+
__all__ = "Dataset"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_FORK_DATASET = """
|
|
7
|
+
mutation ForkDataset(
|
|
8
|
+
$datasetId: ID!
|
|
9
|
+
$branchName: String!
|
|
10
|
+
$versionId: ID
|
|
11
|
+
) {
|
|
12
|
+
fork: forkDataset(
|
|
13
|
+
datasetId: $datasetId
|
|
14
|
+
branchName: $branchName
|
|
15
|
+
versionId: $versionId
|
|
16
|
+
) {
|
|
17
|
+
dataset {
|
|
18
|
+
id
|
|
19
|
+
name
|
|
20
|
+
description
|
|
21
|
+
projectId
|
|
22
|
+
branchName
|
|
23
|
+
parentDatasetId
|
|
24
|
+
forkedAt
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_CREATE_DATASET = """
|
|
32
|
+
mutation CreateDataset(
|
|
33
|
+
$projectId: ID!
|
|
34
|
+
$name: String!
|
|
35
|
+
$description: String
|
|
36
|
+
) {
|
|
37
|
+
dataset: createDataset(
|
|
38
|
+
input: {
|
|
39
|
+
projectId: $projectId
|
|
40
|
+
name: $name
|
|
41
|
+
description: $description
|
|
42
|
+
}
|
|
43
|
+
) {
|
|
44
|
+
id
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Dataset(BaseEntity):
|
|
51
|
+
id: str
|
|
52
|
+
name: str
|
|
53
|
+
description: str
|
|
54
|
+
project_id: str
|
|
55
|
+
kilobytes: int
|
|
56
|
+
formatted_bytes: int
|
|
57
|
+
created_at: str
|
|
58
|
+
updated_at: str
|
|
59
|
+
|
|
60
|
+
def create(self, client=None):
|
|
61
|
+
super().create(client=client)
|
|
62
|
+
|
|
63
|
+
assert self.project_id is not None
|
|
64
|
+
results = self.client.execute(
|
|
65
|
+
_CREATE_DATASET,
|
|
66
|
+
params={
|
|
67
|
+
"projectId": self.project_id,
|
|
68
|
+
"name": self.name,
|
|
69
|
+
"description": self.description,
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.id = results.get("dataset").get("id")
|
|
74
|
+
|
|
75
|
+
def fork(self, branch_name: str, version_id: str = None, client=None):
|
|
76
|
+
"""
|
|
77
|
+
Forks this dataset into a branch and returns it as a new Dataset.
|
|
78
|
+
|
|
79
|
+
The branch shares the parent's storage objects and copies its
|
|
80
|
+
annotations. Pass `version_id` to start the branch from a version's
|
|
81
|
+
reconstructed state instead of the live state. The returned dataset
|
|
82
|
+
can be used anywhere a dataset id is accepted (e.g. uploads).
|
|
83
|
+
|
|
84
|
+
Note: branch attributes (`branch_name`, `parent_dataset_id`,
|
|
85
|
+
`forked_at`) are set dynamically on the returned instance; they are
|
|
86
|
+
deliberately kept out of the class annotations so the generated
|
|
87
|
+
`DatasetFields` fragment stays compatible with servers that predate
|
|
88
|
+
branching.
|
|
89
|
+
"""
|
|
90
|
+
assert self.id is not None
|
|
91
|
+
if client:
|
|
92
|
+
self.client = client
|
|
93
|
+
assert self.client is not None
|
|
94
|
+
|
|
95
|
+
results = self.client.execute(
|
|
96
|
+
_FORK_DATASET,
|
|
97
|
+
params={
|
|
98
|
+
"datasetId": self.id,
|
|
99
|
+
"branchName": branch_name,
|
|
100
|
+
"versionId": version_id,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
return Dataset(results.get("fork").get("dataset"), client=self.client)
|