datatailr 0.1.6__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.6/src/datatailr.egg-info → datatailr-0.1.8}/PKG-INFO +37 -4
- {datatailr-0.1.6 → datatailr-0.1.8}/README.md +29 -3
- {datatailr-0.1.6 → datatailr-0.1.8}/pyproject.toml +13 -3
- datatailr-0.1.8/setup.py +19 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/__init__.py +1 -35
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/acl.py +35 -3
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/blob.py +13 -13
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/build/image.py +38 -2
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/dt_json.py +32 -0
- datatailr-0.1.8/src/datatailr/errors.py +27 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/group.py +19 -13
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/logging.py +27 -10
- datatailr-0.1.8/src/datatailr/scheduler/__init__.py +54 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/arguments_cache.py +71 -43
- datatailr-0.1.8/src/datatailr/scheduler/base.py +371 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/batch.py +141 -19
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/batch_decorator.py +53 -24
- {datatailr-0.1.6 → 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.6 → datatailr-0.1.8}/src/datatailr/scheduler/utils.py +3 -1
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/user.py +30 -17
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/utils.py +20 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/wrapper.py +0 -6
- {datatailr-0.1.6 → datatailr-0.1.8/src/datatailr.egg-info}/PKG-INFO +37 -4
- {datatailr-0.1.6 → 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.6/src/sbin/run_job.py → datatailr-0.1.8/src/sbin/datatailr_run_batch.py +5 -20
- datatailr-0.1.6/setup.py +0 -10
- datatailr-0.1.6/src/datatailr/errors.py +0 -10
- datatailr-0.1.6/src/datatailr/scheduler/__init__.py +0 -38
- datatailr-0.1.6/src/datatailr/scheduler/base.py +0 -245
- datatailr-0.1.6/src/datatailr.egg-info/entry_points.txt +0 -2
- datatailr-0.1.6/src/datatailr.egg-info/requires.txt +0 -8
- datatailr-0.1.6/src/datatailr.egg-info/top_level.txt +0 -2
- datatailr-0.1.6/src/test_module/__init__.py +0 -17
- datatailr-0.1.6/src/test_module/test_submodule.py +0 -38
- {datatailr-0.1.6 → datatailr-0.1.8}/LICENSE +0 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/setup.cfg +0 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/build/__init__.py +0 -0
- {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/version.py +0 -0
- {datatailr-0.1.6 → 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>
|
|
43
|
+
|
|
44
|
+
---
|
|
32
45
|
|
|
33
|
-
___
|
|
34
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>
|
|
6
|
+
|
|
7
|
+
---
|
|
2
8
|
|
|
3
|
-
___
|
|
4
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, mock_cli_tool
|
|
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,9 +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
|
-
if isinstance(
|
|
78
|
+
if isinstance(__client__, mock_cli_tool):
|
|
73
79
|
return
|
|
74
|
-
group =
|
|
80
|
+
group = __client__.get(self.name)
|
|
75
81
|
if group:
|
|
76
82
|
self.__name = group["name"]
|
|
77
83
|
self.__members = group["members"]
|
|
@@ -99,33 +105,33 @@ class Group:
|
|
|
99
105
|
|
|
100
106
|
@staticmethod
|
|
101
107
|
def add(name: str) -> Optional["Group"]:
|
|
102
|
-
|
|
108
|
+
__client__.add(name)
|
|
103
109
|
return Group.get(name)
|
|
104
110
|
|
|
105
111
|
@staticmethod
|
|
106
112
|
def ls() -> list:
|
|
107
|
-
groups =
|
|
113
|
+
groups = __client__.ls()
|
|
108
114
|
return [Group.get(group["name"]) for group in groups]
|
|
109
115
|
|
|
110
116
|
@staticmethod
|
|
111
117
|
def remove(name: str) -> None:
|
|
112
|
-
|
|
118
|
+
__client__.rm(name)
|
|
113
119
|
return None
|
|
114
120
|
|
|
115
121
|
@staticmethod
|
|
116
122
|
def exists(name: str) -> bool:
|
|
117
|
-
return
|
|
123
|
+
return __client__.exists(name)
|
|
118
124
|
|
|
119
125
|
def add_users(self, usernames: list) -> None:
|
|
120
126
|
if not self.name:
|
|
121
127
|
raise ValueError("Name is not set. Cannot add users.")
|
|
122
|
-
|
|
128
|
+
__client__.add_users(self.name, ",".join(usernames))
|
|
123
129
|
self.__refresh__()
|
|
124
130
|
|
|
125
131
|
def remove_users(self, usernames: list) -> None:
|
|
126
132
|
if not self.name:
|
|
127
133
|
raise ValueError("Name is not set. Cannot remove users.")
|
|
128
|
-
|
|
134
|
+
__client__.rm_users(self.name, ",".join(usernames))
|
|
129
135
|
self.__refresh__()
|
|
130
136
|
|
|
131
137
|
def is_member(self, user) -> bool:
|