datatailr 0.1.5__tar.gz → 0.1.8__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.
Potentially problematic release.
This version of datatailr might be problematic. Click here for more details.
- {datatailr-0.1.5/src/datatailr.egg-info → datatailr-0.1.8}/PKG-INFO +38 -5
- {datatailr-0.1.5 → datatailr-0.1.8}/README.md +30 -4
- {datatailr-0.1.5 → datatailr-0.1.8}/pyproject.toml +13 -3
- datatailr-0.1.8/setup.py +19 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/__init__.py +1 -35
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/acl.py +35 -3
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/blob.py +13 -13
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/build/image.py +38 -2
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/dt_json.py +32 -0
- datatailr-0.1.8/src/datatailr/errors.py +27 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/group.py +20 -12
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/logging.py +27 -10
- datatailr-0.1.8/src/datatailr/scheduler/__init__.py +54 -0
- datatailr-0.1.8/src/datatailr/scheduler/arguments_cache.py +169 -0
- datatailr-0.1.8/src/datatailr/scheduler/base.py +371 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/scheduler/batch.py +155 -19
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/scheduler/batch_decorator.py +56 -26
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/scheduler/constants.py +1 -1
- datatailr-0.1.8/src/datatailr/scheduler/schedule.py +117 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/scheduler/utils.py +3 -1
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/user.py +34 -14
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/utils.py +20 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/wrapper.py +10 -10
- {datatailr-0.1.5 → datatailr-0.1.8/src/datatailr.egg-info}/PKG-INFO +38 -5
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr.egg-info/SOURCES.txt +4 -3
- datatailr-0.1.8/src/datatailr.egg-info/entry_points.txt +4 -0
- datatailr-0.1.8/src/datatailr.egg-info/requires.txt +15 -0
- datatailr-0.1.8/src/datatailr.egg-info/top_level.txt +1 -0
- datatailr-0.1.8/src/sbin/datatailr_run.py +147 -0
- datatailr-0.1.8/src/sbin/datatailr_run_app.py +28 -0
- datatailr-0.1.5/src/sbin/run_job.py → datatailr-0.1.8/src/sbin/datatailr_run_batch.py +5 -20
- datatailr-0.1.5/setup.py +0 -10
- datatailr-0.1.5/src/datatailr/errors.py +0 -10
- datatailr-0.1.5/src/datatailr/scheduler/__init__.py +0 -38
- datatailr-0.1.5/src/datatailr/scheduler/arguments_cache.py +0 -126
- datatailr-0.1.5/src/datatailr/scheduler/base.py +0 -245
- datatailr-0.1.5/src/datatailr.egg-info/entry_points.txt +0 -2
- datatailr-0.1.5/src/datatailr.egg-info/requires.txt +0 -8
- datatailr-0.1.5/src/datatailr.egg-info/top_level.txt +0 -2
- datatailr-0.1.5/src/test_module/__init__.py +0 -17
- datatailr-0.1.5/src/test_module/test_submodule.py +0 -38
- {datatailr-0.1.5 → datatailr-0.1.8}/LICENSE +0 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/setup.cfg +0 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/build/__init__.py +0 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr/version.py +0 -0
- {datatailr-0.1.5 → datatailr-0.1.8}/src/datatailr.egg-info/dependency_links.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datatailr
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Ready-to-Use Platform That Drives Business Insights
|
|
5
5
|
Author-email: Datatailr <info@datatailr.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,15 +26,27 @@ Requires-Dist: mypy; extra == "dev"
|
|
|
26
26
|
Requires-Dist: types-setuptools; extra == "dev"
|
|
27
27
|
Requires-Dist: toml; extra == "dev"
|
|
28
28
|
Requires-Dist: coverage; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx-rtd-theme; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
31
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "dev"
|
|
32
|
+
Requires-Dist: sphinx-autosummary; extra == "dev"
|
|
33
|
+
Requires-Dist: sphinx-design; extra == "dev"
|
|
34
|
+
Requires-Dist: sphinx-copybutton; extra == "dev"
|
|
35
|
+
Requires-Dist: myst-parser; extra == "dev"
|
|
29
36
|
Dynamic: license-file
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
<div style="text-align: center;">
|
|
39
|
+
<a href="https://www.datatailr.com/" target="_blank">
|
|
40
|
+
<img src="https://s3.eu-west-1.amazonaws.com/datatailr.com/assets/datatailr-logo.svg" alt="Datatailr Logo" />
|
|
41
|
+
</a>
|
|
42
|
+
</div>
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
**Datatailr empowers your team to streamline analytics and data workflows
|
|
35
47
|
from idea to production without infrastructure hurdles.**
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
# What is Datatailr?
|
|
38
50
|
|
|
39
51
|
Datatailr is a platform that simplifies the process of building and deploying data applications.
|
|
40
52
|
|
|
@@ -69,7 +81,28 @@ print(datatailr.__provider__)
|
|
|
69
81
|
|
|
70
82
|
|
|
71
83
|
## Quickstart
|
|
84
|
+
The following example shows how to create a simple data pipeline using the Datatailr Python package.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from datatailr.scheduler import batch, Batch
|
|
88
|
+
|
|
89
|
+
@batch()
|
|
90
|
+
def func_no_args() -> str:
|
|
91
|
+
return "no_args"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@batch()
|
|
95
|
+
def func_with_args(a: int, b: float) -> str:
|
|
96
|
+
return f"args: {a}, {b}"
|
|
97
|
+
|
|
98
|
+
with Batch(name="MY test DAG", local_run=True) as dag:
|
|
99
|
+
for n in range(2):
|
|
100
|
+
res1 = func_no_args().alias(f"func_{n}")
|
|
101
|
+
res2 = func_with_args(1, res1).alias(f"func_with_args_{n}")
|
|
102
|
+
```
|
|
72
103
|
|
|
104
|
+
Running this code will create a graph of jobs and execute it.
|
|
105
|
+
Each node on the graph represents a job, which in turn is a call to a function decorated with `@batch()`.
|
|
73
106
|
|
|
74
107
|
___
|
|
75
108
|
Visit [our website](https://www.datatailr.com/) for more!
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
<div style="text-align: center;">
|
|
2
|
+
<a href="https://www.datatailr.com/" target="_blank">
|
|
3
|
+
<img src="https://s3.eu-west-1.amazonaws.com/datatailr.com/assets/datatailr-logo.svg" alt="Datatailr Logo" />
|
|
4
|
+
</a>
|
|
5
|
+
</div>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Datatailr empowers your team to streamline analytics and data workflows
|
|
5
10
|
from idea to production without infrastructure hurdles.**
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
# What is Datatailr?
|
|
8
13
|
|
|
9
14
|
Datatailr is a platform that simplifies the process of building and deploying data applications.
|
|
10
15
|
|
|
@@ -39,7 +44,28 @@ print(datatailr.__provider__)
|
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
## Quickstart
|
|
47
|
+
The following example shows how to create a simple data pipeline using the Datatailr Python package.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from datatailr.scheduler import batch, Batch
|
|
51
|
+
|
|
52
|
+
@batch()
|
|
53
|
+
def func_no_args() -> str:
|
|
54
|
+
return "no_args"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@batch()
|
|
58
|
+
def func_with_args(a: int, b: float) -> str:
|
|
59
|
+
return f"args: {a}, {b}"
|
|
60
|
+
|
|
61
|
+
with Batch(name="MY test DAG", local_run=True) as dag:
|
|
62
|
+
for n in range(2):
|
|
63
|
+
res1 = func_no_args().alias(f"func_{n}")
|
|
64
|
+
res2 = func_with_args(1, res1).alias(f"func_with_args_{n}")
|
|
65
|
+
```
|
|
42
66
|
|
|
67
|
+
Running this code will create a graph of jobs and execute it.
|
|
68
|
+
Each node on the graph represents a job, which in turn is a call to a function decorated with `@batch()`.
|
|
43
69
|
|
|
44
70
|
___
|
|
45
71
|
Visit [our website](https://www.datatailr.com/) for more!
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools
|
|
2
|
+
requires = ["setuptools", "wheel", "toml"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datatailr"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "Ready-to-Use Platform That Drives Business Insights"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -34,7 +34,9 @@ homepage = "https://www.datatailr.com/"
|
|
|
34
34
|
documentation = "https://docs.datatailr.com/"
|
|
35
35
|
|
|
36
36
|
[project.scripts]
|
|
37
|
-
|
|
37
|
+
datatailr_run = "datatailr.sbin.datatailr_run:main"
|
|
38
|
+
datatailr_run_batch = "datatailr.sbin.datatailr_run_batch:run"
|
|
39
|
+
datatailr_run_app = "datatailr.sbin.datatailr_run_app:run"
|
|
38
40
|
|
|
39
41
|
[project.optional-dependencies]
|
|
40
42
|
dev = [
|
|
@@ -44,6 +46,13 @@ dev = [
|
|
|
44
46
|
"types-setuptools",
|
|
45
47
|
"toml",
|
|
46
48
|
"coverage",
|
|
49
|
+
"sphinx-rtd-theme",
|
|
50
|
+
"sphinx",
|
|
51
|
+
"sphinx-autodoc-typehints",
|
|
52
|
+
"sphinx-autosummary",
|
|
53
|
+
"sphinx-design",
|
|
54
|
+
"sphinx-copybutton",
|
|
55
|
+
"myst-parser"
|
|
47
56
|
]
|
|
48
57
|
|
|
49
58
|
[tool.ruff]
|
|
@@ -51,6 +60,7 @@ src = [
|
|
|
51
60
|
"src",
|
|
52
61
|
"../../tests/src/python"
|
|
53
62
|
]
|
|
63
|
+
|
|
54
64
|
lint.ignore = ["F841"]
|
|
55
65
|
show-fixes = true
|
|
56
66
|
fix = true
|
datatailr-0.1.8/setup.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from datatailr import __version__ # type: ignore
|
|
2
|
+
from setuptools import find_packages, setup
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name="datatailr",
|
|
6
|
+
version=__version__,
|
|
7
|
+
packages=find_packages(where="src", exclude=["test_module", "test_module.*"]),
|
|
8
|
+
package_dir={"": "src"},
|
|
9
|
+
data_files=[
|
|
10
|
+
(
|
|
11
|
+
"/datatailr/sbin",
|
|
12
|
+
[
|
|
13
|
+
"src/sbin/datatailr_run.py",
|
|
14
|
+
"src/sbin/datatailr_run_batch.py",
|
|
15
|
+
"src/sbin/datatailr_run_app.py",
|
|
16
|
+
],
|
|
17
|
+
)
|
|
18
|
+
],
|
|
19
|
+
)
|
|
@@ -8,30 +8,12 @@
|
|
|
8
8
|
# of this file, in parts or full, via any medium is strictly prohibited.
|
|
9
9
|
# *************************************************************************
|
|
10
10
|
|
|
11
|
-
from datatailr.wrapper import
|
|
12
|
-
dt__Blob,
|
|
13
|
-
dt__Dns,
|
|
14
|
-
dt__Email,
|
|
15
|
-
dt__Group,
|
|
16
|
-
dt__Job,
|
|
17
|
-
dt__Kv,
|
|
18
|
-
dt__Log,
|
|
19
|
-
dt__Node,
|
|
20
|
-
dt__Registry,
|
|
21
|
-
dt__Service,
|
|
22
|
-
dt__Settings,
|
|
23
|
-
dt__Sms,
|
|
24
|
-
dt__System,
|
|
25
|
-
dt__Tag,
|
|
26
|
-
dt__User,
|
|
27
|
-
mock_cli_tool,
|
|
28
|
-
)
|
|
11
|
+
from datatailr.wrapper import dt__System, mock_cli_tool
|
|
29
12
|
from datatailr.group import Group
|
|
30
13
|
from datatailr.user import User
|
|
31
14
|
from datatailr.acl import ACL
|
|
32
15
|
from datatailr.blob import Blob
|
|
33
16
|
from datatailr.build import Image
|
|
34
|
-
from datatailr.dt_json import dt_json
|
|
35
17
|
from datatailr.utils import Environment, is_dt_installed
|
|
36
18
|
from datatailr.version import __version__
|
|
37
19
|
|
|
@@ -50,21 +32,5 @@ __all__ = [
|
|
|
50
32
|
"User",
|
|
51
33
|
"__version__",
|
|
52
34
|
"__provider__",
|
|
53
|
-
"dt__Blob",
|
|
54
|
-
"dt__Dns",
|
|
55
|
-
"dt__Email",
|
|
56
|
-
"dt__Group",
|
|
57
|
-
"dt__Job",
|
|
58
|
-
"dt__Kv",
|
|
59
|
-
"dt__Log",
|
|
60
|
-
"dt__Node",
|
|
61
|
-
"dt__Registry",
|
|
62
|
-
"dt__Service",
|
|
63
|
-
"dt__Settings",
|
|
64
|
-
"dt__Sms",
|
|
65
|
-
"dt__System",
|
|
66
|
-
"dt__Tag",
|
|
67
|
-
"dt__User",
|
|
68
|
-
"dt_json",
|
|
69
35
|
"is_dt_installed",
|
|
70
36
|
]
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
# of this file, in parts or full, via any medium is strictly prohibited.
|
|
9
9
|
# *************************************************************************
|
|
10
10
|
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
11
13
|
import json
|
|
12
14
|
from typing import List, Optional, Union
|
|
13
15
|
|
|
@@ -40,9 +42,9 @@ class ACL:
|
|
|
40
42
|
self.__user_can_write = False
|
|
41
43
|
self.__world_readable = False
|
|
42
44
|
|
|
43
|
-
self.
|
|
45
|
+
self.__parse_permissions_string()
|
|
44
46
|
|
|
45
|
-
def
|
|
47
|
+
def __parse_permissions_string(self):
|
|
46
48
|
"""
|
|
47
49
|
Parse the permissions and set the corresponding flags.
|
|
48
50
|
"""
|
|
@@ -59,6 +61,19 @@ class ACL:
|
|
|
59
61
|
self.__world_readable = self.permissions[4] == "r"
|
|
60
62
|
self.__world_writable = self.permissions[5] == "w"
|
|
61
63
|
|
|
64
|
+
def _set_permissions_string(self):
|
|
65
|
+
"""
|
|
66
|
+
Set the permissions string based on the current flags.
|
|
67
|
+
"""
|
|
68
|
+
self.permissions = (
|
|
69
|
+
f"{'r' if self.__user_can_read else '-'}"
|
|
70
|
+
f"{'w' if self.__user_can_write else '-'}"
|
|
71
|
+
f"{'r' if self.__group_can_read else '-'}"
|
|
72
|
+
f"{'w' if self.__group_can_write else '-'}"
|
|
73
|
+
f"{'r' if self.__world_readable else '-'}"
|
|
74
|
+
f"{'w' if self.__world_writable else '-'}"
|
|
75
|
+
)
|
|
76
|
+
|
|
62
77
|
def __repr__(self):
|
|
63
78
|
return (
|
|
64
79
|
f"ACL(user={self.user}, group={self.group}, permissions={self.permissions})"
|
|
@@ -67,7 +82,7 @@ class ACL:
|
|
|
67
82
|
def to_dict(self):
|
|
68
83
|
return {
|
|
69
84
|
"user": self.user.name,
|
|
70
|
-
"group": self.group.name if self.group else
|
|
85
|
+
"group": self.group.name if self.group else "dtusers",
|
|
71
86
|
"group_can_read": self.__group_can_read,
|
|
72
87
|
"group_can_write": self.__group_can_write,
|
|
73
88
|
"user_can_read": self.__user_can_read,
|
|
@@ -76,5 +91,22 @@ class ACL:
|
|
|
76
91
|
"world_writable": self.__world_writable,
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_dict(cls, acl_dict: dict) -> ACL:
|
|
96
|
+
"""
|
|
97
|
+
Create an ACL instance from a dictionary.
|
|
98
|
+
"""
|
|
99
|
+
user = User(acl_dict["user"])
|
|
100
|
+
group = Group.get(acl_dict["group"]) if "group" in acl_dict else None
|
|
101
|
+
acl = cls(user=user, group=group)
|
|
102
|
+
acl.__group_can_read = acl_dict.get("group_can_read", False)
|
|
103
|
+
acl.__group_can_write = acl_dict.get("group_can_write", False)
|
|
104
|
+
acl.__user_can_read = acl_dict.get("user_can_read", False)
|
|
105
|
+
acl.__user_can_write = acl_dict.get("user_can_write", False)
|
|
106
|
+
acl.__world_readable = acl_dict.get("world_readable", False)
|
|
107
|
+
acl.__world_writable = acl_dict.get("world_writable", False)
|
|
108
|
+
acl._set_permissions_string()
|
|
109
|
+
return acl
|
|
110
|
+
|
|
79
111
|
def to_json(self):
|
|
80
112
|
return json.dumps(self.to_dict())
|
|
@@ -12,12 +12,13 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import tempfile
|
|
14
14
|
|
|
15
|
-
from datatailr import dt__Blob
|
|
15
|
+
from datatailr.wrapper import dt__Blob
|
|
16
16
|
|
|
17
|
+
# Datatailr Blob API Client
|
|
18
|
+
__client__ = dt__Blob()
|
|
17
19
|
|
|
18
|
-
class Blob:
|
|
19
|
-
__blob_storage__ = dt__Blob()
|
|
20
20
|
|
|
21
|
+
class Blob:
|
|
21
22
|
def ls(self, path: str) -> list[str]:
|
|
22
23
|
"""
|
|
23
24
|
List files in the specified path.
|
|
@@ -25,7 +26,7 @@ class Blob:
|
|
|
25
26
|
:param path: The path to list files from.
|
|
26
27
|
:return: A list of file names in the specified path.
|
|
27
28
|
"""
|
|
28
|
-
return
|
|
29
|
+
return __client__.ls(path)
|
|
29
30
|
|
|
30
31
|
def get_file(self, name: str, path: str):
|
|
31
32
|
"""
|
|
@@ -35,7 +36,7 @@ class Blob:
|
|
|
35
36
|
name (str): The name of the blob to retrieve.
|
|
36
37
|
path (str): The path to store the blob as a file.
|
|
37
38
|
"""
|
|
38
|
-
return
|
|
39
|
+
return __client__.cp(f"blob://{name}", path)
|
|
39
40
|
|
|
40
41
|
def put_file(self, name: str, path: str):
|
|
41
42
|
"""
|
|
@@ -45,7 +46,7 @@ class Blob:
|
|
|
45
46
|
name (str): The name of the blob to create.
|
|
46
47
|
path (str): The path of the local file to copy.
|
|
47
48
|
"""
|
|
48
|
-
return
|
|
49
|
+
return __client__.cp(path, f"blob://{name}")
|
|
49
50
|
|
|
50
51
|
def exists(self, name: str) -> bool:
|
|
51
52
|
"""
|
|
@@ -57,7 +58,7 @@ class Blob:
|
|
|
57
58
|
Returns:
|
|
58
59
|
bool: True if the blob exists, False otherwise.
|
|
59
60
|
"""
|
|
60
|
-
return
|
|
61
|
+
return __client__.exists(name)
|
|
61
62
|
|
|
62
63
|
def delete(self, name: str):
|
|
63
64
|
"""
|
|
@@ -66,7 +67,7 @@ class Blob:
|
|
|
66
67
|
Args:
|
|
67
68
|
name (str): The name of the blob to delete.
|
|
68
69
|
"""
|
|
69
|
-
return
|
|
70
|
+
return __client__.rm(name)
|
|
70
71
|
|
|
71
72
|
def get_blob(self, name: str):
|
|
72
73
|
"""
|
|
@@ -81,9 +82,9 @@ class Blob:
|
|
|
81
82
|
# Since direct reading and writting of blobs is not implemented yet, we are using a temporary file.
|
|
82
83
|
# This is a workaround to allow reading the blob content directly from the blob storage.
|
|
83
84
|
|
|
84
|
-
with tempfile.NamedTemporaryFile(delete=
|
|
85
|
+
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
|
85
86
|
self.get_file(name, temp_file.name)
|
|
86
|
-
with open(temp_file.name, "
|
|
87
|
+
with open(temp_file.name, "r") as f:
|
|
87
88
|
return f.read()
|
|
88
89
|
|
|
89
90
|
def put_blob(self, name: str, blob):
|
|
@@ -96,8 +97,7 @@ class Blob:
|
|
|
96
97
|
"""
|
|
97
98
|
# Since direct reading and writting of blobs is not implemented yet, we are using a temporary file.
|
|
98
99
|
# This is a workaround to allow writing the blob content directly to the blob storage.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
with open(temp_file.name, "wb") as f:
|
|
100
|
+
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
|
101
|
+
with open(temp_file.name, "w") as f:
|
|
102
102
|
f.write(blob)
|
|
103
103
|
self.put_file(name, temp_file.name)
|
|
@@ -25,9 +25,13 @@ class Image:
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
27
|
acl: Optional[ACL] = None,
|
|
28
|
-
python_requirements: str = "",
|
|
28
|
+
python_requirements: str | list[str] = "",
|
|
29
29
|
build_script_pre: str = "",
|
|
30
30
|
build_script_post: str = "",
|
|
31
|
+
branch_name: Optional[str] = None,
|
|
32
|
+
commit_hash: Optional[str] = None,
|
|
33
|
+
path_to_repo: Optional[str] = None,
|
|
34
|
+
path_to_module: Optional[str] = None,
|
|
31
35
|
):
|
|
32
36
|
if acl is None:
|
|
33
37
|
signed_user = User.signed_user()
|
|
@@ -39,9 +43,11 @@ class Image:
|
|
|
39
43
|
raise TypeError("acl must be an instance of ACL.")
|
|
40
44
|
self.acl = acl or ACL(signed_user)
|
|
41
45
|
|
|
42
|
-
if os.path.isfile(python_requirements):
|
|
46
|
+
if isinstance(python_requirements, str) and os.path.isfile(python_requirements):
|
|
43
47
|
with open(python_requirements, "r") as f:
|
|
44
48
|
python_requirements = f.read()
|
|
49
|
+
elif isinstance(python_requirements, list):
|
|
50
|
+
python_requirements = "\n".join(python_requirements)
|
|
45
51
|
if not isinstance(python_requirements, str):
|
|
46
52
|
raise TypeError(
|
|
47
53
|
"python_requirements must be a string or a file path to a requirements file."
|
|
@@ -65,10 +71,36 @@ class Image:
|
|
|
65
71
|
"build_script_post must be a string or a file path to a script file."
|
|
66
72
|
)
|
|
67
73
|
self.build_script_post = build_script_post
|
|
74
|
+
self.branch_name = branch_name
|
|
75
|
+
self.commit_hash = commit_hash
|
|
76
|
+
self.path_to_repo = path_to_repo
|
|
77
|
+
self.path_to_module = path_to_module
|
|
68
78
|
|
|
69
79
|
def __repr__(self):
|
|
70
80
|
return f"Image(acl={self.acl},)"
|
|
71
81
|
|
|
82
|
+
def update(self, **kwargs):
|
|
83
|
+
for key, value in kwargs.items():
|
|
84
|
+
if key == "acl" and not isinstance(value, ACL):
|
|
85
|
+
raise TypeError("acl must be an instance of ACL.")
|
|
86
|
+
elif key == "python_requirements" and not isinstance(value, str):
|
|
87
|
+
raise TypeError("python_requirements must be a string.")
|
|
88
|
+
elif key == "build_script_pre" and not isinstance(value, str):
|
|
89
|
+
raise TypeError("build_script_pre must be a string.")
|
|
90
|
+
elif key == "build_script_post" and not isinstance(value, str):
|
|
91
|
+
raise TypeError("build_script_post must be a string.")
|
|
92
|
+
elif (
|
|
93
|
+
key in ["branch_name", "commit_hash", "path_to_repo", "path_to_module"]
|
|
94
|
+
and value is not None
|
|
95
|
+
and not isinstance(value, str)
|
|
96
|
+
):
|
|
97
|
+
raise TypeError(f"{key} must be a string or None.")
|
|
98
|
+
if key not in self.__dict__:
|
|
99
|
+
raise AttributeError(
|
|
100
|
+
f"'{self.__class__.__name__}' object has no attribute '{key}'"
|
|
101
|
+
)
|
|
102
|
+
setattr(self, key, value)
|
|
103
|
+
|
|
72
104
|
def to_dict(self):
|
|
73
105
|
"""
|
|
74
106
|
Convert the Image instance to a dictionary representation.
|
|
@@ -78,6 +110,10 @@ class Image:
|
|
|
78
110
|
"python_requirements": self.python_requirements,
|
|
79
111
|
"build_script_pre": self.build_script_pre,
|
|
80
112
|
"build_script_post": self.build_script_post,
|
|
113
|
+
"branch_name": self.branch_name,
|
|
114
|
+
"commit_hash": self.commit_hash,
|
|
115
|
+
"path_to_repo": self.path_to_repo,
|
|
116
|
+
"path_to_module": self.path_to_module,
|
|
81
117
|
}
|
|
82
118
|
|
|
83
119
|
def to_json(self):
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import importlib
|
|
12
12
|
import json
|
|
13
|
+
import base64
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class dt_json:
|
|
@@ -40,3 +41,34 @@ class dt_json:
|
|
|
40
41
|
@classmethod
|
|
41
42
|
def load(cls, json_file):
|
|
42
43
|
return json.load(json_file)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def encode_json(obj) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Encode an object to a JSON string with class information.
|
|
49
|
+
"""
|
|
50
|
+
json_str = json.dumps(
|
|
51
|
+
obj, default=lambda o: o.__dict__ if hasattr(o, "__dict__") else o
|
|
52
|
+
)
|
|
53
|
+
return base64.b64encode(json_str.encode("utf-8")).decode("utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_base64(s: str) -> bool:
|
|
57
|
+
try:
|
|
58
|
+
# Try decoding the string
|
|
59
|
+
base64.b64decode(s, validate=True)
|
|
60
|
+
return True
|
|
61
|
+
except (ValueError, TypeError):
|
|
62
|
+
# If decoding fails, it's not valid Base64
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def decode_json(encoded_str: str):
|
|
67
|
+
"""
|
|
68
|
+
Decode a JSON string back to an object.
|
|
69
|
+
"""
|
|
70
|
+
if is_base64(encoded_str):
|
|
71
|
+
json_str = base64.b64decode(encoded_str.encode("utf-8")).decode("utf-8")
|
|
72
|
+
else:
|
|
73
|
+
json_str = encoded_str
|
|
74
|
+
return json.loads(json_str)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
##########################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 - Datatailr Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This file is part of Datatailr and subject to the terms and conditions
|
|
7
|
+
# defined in 'LICENSE.txt'. Unauthorized copying and/or distribution
|
|
8
|
+
# of this file, in parts or full, via any medium is strictly prohibited.
|
|
9
|
+
##########################################################################
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DatatailrError(Exception):
|
|
13
|
+
"""Base class for all DataTailr exceptions."""
|
|
14
|
+
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BatchJobError(DatatailrError):
|
|
19
|
+
"""Exception raised for errors related to batch jobs."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ScheduleError(DatatailrError):
|
|
25
|
+
"""Exception raised for errors related to scheduling."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
@@ -10,17 +10,25 @@
|
|
|
10
10
|
|
|
11
11
|
from typing import Optional, Union
|
|
12
12
|
|
|
13
|
-
from datatailr import dt__Group
|
|
13
|
+
from datatailr.wrapper import dt__Group, mock_cli_tool
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Datatailr Group API Client
|
|
17
|
+
__client__ = dt__Group()
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class Group:
|
|
17
|
-
"""
|
|
21
|
+
"""
|
|
22
|
+
Representing a Datatailr Group.
|
|
23
|
+
|
|
18
24
|
This class provides methods to interact with the Datatailr Group API.
|
|
19
25
|
It allows you to create, update, delete, and manage groups within the Datatailr platform.
|
|
26
|
+
|
|
20
27
|
Attributes:
|
|
21
28
|
name (str): The name of the group.
|
|
22
29
|
members (list): A list of members in the group.
|
|
23
30
|
group_id (int): The unique identifier for the group.
|
|
31
|
+
|
|
24
32
|
Static Methods:
|
|
25
33
|
add(name: str) -> 'Group':
|
|
26
34
|
Create a new group with the specified name.
|
|
@@ -32,6 +40,7 @@ class Group:
|
|
|
32
40
|
Remove a group by its name.
|
|
33
41
|
exists(name: str) -> bool:
|
|
34
42
|
Check if a group exists by its name.
|
|
43
|
+
|
|
35
44
|
Instance Methods:
|
|
36
45
|
add_users(usernames: list) -> None:
|
|
37
46
|
Add users to the group.
|
|
@@ -39,9 +48,6 @@ class Group:
|
|
|
39
48
|
Remove users from the group.
|
|
40
49
|
"""
|
|
41
50
|
|
|
42
|
-
# Datatailr Group API Client
|
|
43
|
-
__client__ = dt__Group()
|
|
44
|
-
|
|
45
51
|
def __init__(self, name: str):
|
|
46
52
|
self.__name: str = name
|
|
47
53
|
self.__members: list = []
|
|
@@ -69,7 +75,9 @@ class Group:
|
|
|
69
75
|
def __refresh__(self):
|
|
70
76
|
if not self.name:
|
|
71
77
|
raise ValueError("Name is not set. Cannot refresh group.")
|
|
72
|
-
|
|
78
|
+
if isinstance(__client__, mock_cli_tool):
|
|
79
|
+
return
|
|
80
|
+
group = __client__.get(self.name)
|
|
73
81
|
if group:
|
|
74
82
|
self.__name = group["name"]
|
|
75
83
|
self.__members = group["members"]
|
|
@@ -97,33 +105,33 @@ class Group:
|
|
|
97
105
|
|
|
98
106
|
@staticmethod
|
|
99
107
|
def add(name: str) -> Optional["Group"]:
|
|
100
|
-
|
|
108
|
+
__client__.add(name)
|
|
101
109
|
return Group.get(name)
|
|
102
110
|
|
|
103
111
|
@staticmethod
|
|
104
112
|
def ls() -> list:
|
|
105
|
-
groups =
|
|
113
|
+
groups = __client__.ls()
|
|
106
114
|
return [Group.get(group["name"]) for group in groups]
|
|
107
115
|
|
|
108
116
|
@staticmethod
|
|
109
117
|
def remove(name: str) -> None:
|
|
110
|
-
|
|
118
|
+
__client__.rm(name)
|
|
111
119
|
return None
|
|
112
120
|
|
|
113
121
|
@staticmethod
|
|
114
122
|
def exists(name: str) -> bool:
|
|
115
|
-
return
|
|
123
|
+
return __client__.exists(name)
|
|
116
124
|
|
|
117
125
|
def add_users(self, usernames: list) -> None:
|
|
118
126
|
if not self.name:
|
|
119
127
|
raise ValueError("Name is not set. Cannot add users.")
|
|
120
|
-
|
|
128
|
+
__client__.add_users(self.name, ",".join(usernames))
|
|
121
129
|
self.__refresh__()
|
|
122
130
|
|
|
123
131
|
def remove_users(self, usernames: list) -> None:
|
|
124
132
|
if not self.name:
|
|
125
133
|
raise ValueError("Name is not set. Cannot remove users.")
|
|
126
|
-
|
|
134
|
+
__client__.rm_users(self.name, ",".join(usernames))
|
|
127
135
|
self.__refresh__()
|
|
128
136
|
|
|
129
137
|
def is_member(self, user) -> bool:
|