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.

Files changed (45) hide show
  1. {datatailr-0.1.6/src/datatailr.egg-info → datatailr-0.1.8}/PKG-INFO +37 -4
  2. {datatailr-0.1.6 → datatailr-0.1.8}/README.md +29 -3
  3. {datatailr-0.1.6 → datatailr-0.1.8}/pyproject.toml +13 -3
  4. datatailr-0.1.8/setup.py +19 -0
  5. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/__init__.py +1 -35
  6. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/acl.py +35 -3
  7. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/blob.py +13 -13
  8. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/build/image.py +38 -2
  9. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/dt_json.py +32 -0
  10. datatailr-0.1.8/src/datatailr/errors.py +27 -0
  11. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/group.py +19 -13
  12. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/logging.py +27 -10
  13. datatailr-0.1.8/src/datatailr/scheduler/__init__.py +54 -0
  14. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/arguments_cache.py +71 -43
  15. datatailr-0.1.8/src/datatailr/scheduler/base.py +371 -0
  16. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/batch.py +141 -19
  17. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/batch_decorator.py +53 -24
  18. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/constants.py +1 -1
  19. datatailr-0.1.8/src/datatailr/scheduler/schedule.py +117 -0
  20. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/scheduler/utils.py +3 -1
  21. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/user.py +30 -17
  22. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/utils.py +20 -0
  23. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/wrapper.py +0 -6
  24. {datatailr-0.1.6 → datatailr-0.1.8/src/datatailr.egg-info}/PKG-INFO +37 -4
  25. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr.egg-info/SOURCES.txt +4 -3
  26. datatailr-0.1.8/src/datatailr.egg-info/entry_points.txt +4 -0
  27. datatailr-0.1.8/src/datatailr.egg-info/requires.txt +15 -0
  28. datatailr-0.1.8/src/datatailr.egg-info/top_level.txt +1 -0
  29. datatailr-0.1.8/src/sbin/datatailr_run.py +147 -0
  30. datatailr-0.1.8/src/sbin/datatailr_run_app.py +28 -0
  31. datatailr-0.1.6/src/sbin/run_job.py → datatailr-0.1.8/src/sbin/datatailr_run_batch.py +5 -20
  32. datatailr-0.1.6/setup.py +0 -10
  33. datatailr-0.1.6/src/datatailr/errors.py +0 -10
  34. datatailr-0.1.6/src/datatailr/scheduler/__init__.py +0 -38
  35. datatailr-0.1.6/src/datatailr/scheduler/base.py +0 -245
  36. datatailr-0.1.6/src/datatailr.egg-info/entry_points.txt +0 -2
  37. datatailr-0.1.6/src/datatailr.egg-info/requires.txt +0 -8
  38. datatailr-0.1.6/src/datatailr.egg-info/top_level.txt +0 -2
  39. datatailr-0.1.6/src/test_module/__init__.py +0 -17
  40. datatailr-0.1.6/src/test_module/test_submodule.py +0 -38
  41. {datatailr-0.1.6 → datatailr-0.1.8}/LICENSE +0 -0
  42. {datatailr-0.1.6 → datatailr-0.1.8}/setup.cfg +0 -0
  43. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/build/__init__.py +0 -0
  44. {datatailr-0.1.6 → datatailr-0.1.8}/src/datatailr/version.py +0 -0
  45. {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.6
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
- [![Datatailr Logo](https://framerusercontent.com/images/6CmFcG0o2ryoa5cMuDsvwESVWA.svg)](https://www.datatailr.com/)
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
- ## What is Datatailr?
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
- [![Datatailr Logo](https://framerusercontent.com/images/6CmFcG0o2ryoa5cMuDsvwESVWA.svg)](https://www.datatailr.com/)
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
- ## What is Datatailr?
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>=60", "wheel", "toml"]
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.6"
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
- run_dt_job = "datatailr.sbin.run_job:main"
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
@@ -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.__parse_permissions()
45
+ self.__parse_permissions_string()
44
46
 
45
- def __parse_permissions(self):
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 self.user.primary_group.name,
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 self.__blob_storage__.ls(path)
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 self.__blob_storage__.cp(f"blob:///{name}", path)
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 self.__blob_storage__.cp(path, f"blob:///{name}")
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 self.__blob_storage__.exists(name)
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 self.__blob_storage__.rm(name)
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=False) as temp_file:
85
+ with tempfile.NamedTemporaryFile(delete=True) as temp_file:
85
86
  self.get_file(name, temp_file.name)
86
- with open(temp_file.name, "rb") as f:
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
- with tempfile.NamedTemporaryFile(delete=False) as temp_file:
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
- """Representing a Datatailr Group
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(Group.__client__, mock_cli_tool):
78
+ if isinstance(__client__, mock_cli_tool):
73
79
  return
74
- group = Group.__client__.get(self.name)
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
- Group.__client__.add(name)
108
+ __client__.add(name)
103
109
  return Group.get(name)
104
110
 
105
111
  @staticmethod
106
112
  def ls() -> list:
107
- groups = Group.__client__.ls()
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
- Group.__client__.rm(name)
118
+ __client__.rm(name)
113
119
  return None
114
120
 
115
121
  @staticmethod
116
122
  def exists(name: str) -> bool:
117
- return Group.__client__.exists(name)
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
- Group.__client__.add_users(self.name, ",".join(usernames))
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
- Group.__client__.rm_users(self.name, ",".join(usernames))
134
+ __client__.rm_users(self.name, ",".join(usernames))
129
135
  self.__refresh__()
130
136
 
131
137
  def is_member(self, user) -> bool: