datatailr 0.1.6__py3-none-any.whl → 0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of datatailr might be problematic. Click here for more details.

datatailr/__init__.py CHANGED
@@ -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
  ]
datatailr/acl.py CHANGED
@@ -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())
datatailr/blob.py CHANGED
@@ -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)
datatailr/build/image.py CHANGED
@@ -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):
datatailr/dt_json.py CHANGED
@@ -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)
datatailr/errors.py CHANGED
@@ -1,3 +1,14 @@
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
+
1
12
  class DatatailrError(Exception):
2
13
  """Base class for all DataTailr exceptions."""
3
14
 
@@ -8,3 +19,9 @@ class BatchJobError(DatatailrError):
8
19
  """Exception raised for errors related to batch jobs."""
9
20
 
10
21
  pass
22
+
23
+
24
+ class ScheduleError(DatatailrError):
25
+ """Exception raised for errors related to scheduling."""
26
+
27
+ pass
datatailr/group.py CHANGED
@@ -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:
datatailr/logging.py CHANGED
@@ -13,6 +13,8 @@ import os
13
13
  from logging import StreamHandler
14
14
  from logging.handlers import RotatingFileHandler
15
15
  from typing import Optional
16
+ from datatailr import User
17
+ from datatailr.wrapper import dt__Tag, mock_cli_tool
16
18
 
17
19
 
18
20
  def get_log_level() -> int:
@@ -31,12 +33,34 @@ def get_log_level() -> int:
31
33
  return logging.INFO
32
34
 
33
35
 
36
+ tag = dt__Tag()
37
+ if isinstance(tag, mock_cli_tool):
38
+ node_name = "local"
39
+ node_ip = "0.0.0.0"
40
+ job_name = "local_job"
41
+
42
+ else:
43
+ node_name = tag.get("node_name")
44
+ node_ip = tag.get("node_ip")
45
+ job_name = os.getenv("DATATAILR_JOB_NAME", "unknown_job")
46
+
47
+ try:
48
+ user = User.signed_user().name
49
+ except Exception:
50
+ import getpass
51
+
52
+ user = getpass.getuser()
53
+
54
+ LOG_FORMAT = f"%(asctime)s - %(levelname)s - {node_name}:{node_ip} - {user} - {job_name} - %(name)s - [Line %(lineno)d]: %(message)s"
55
+
56
+
34
57
  class DatatailrLogger:
35
58
  def __init__(
36
59
  self,
37
60
  name: str,
38
61
  log_file: Optional[str] = None,
39
62
  log_level: int = get_log_level(),
63
+ log_format: str = LOG_FORMAT,
40
64
  ):
41
65
  """
42
66
  Initialize the DatatailrLogger.
@@ -51,9 +75,7 @@ class DatatailrLogger:
51
75
  # Stream handler for stdout/stderr
52
76
  stream_handler = StreamHandler()
53
77
  stream_handler.setLevel(log_level)
54
- stream_formatter = logging.Formatter(
55
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
56
- )
78
+ stream_formatter = logging.Formatter(log_format)
57
79
  stream_handler.setFormatter(stream_formatter)
58
80
  self.logger.addHandler(stream_handler)
59
81
 
@@ -63,9 +85,7 @@ class DatatailrLogger:
63
85
  log_file, maxBytes=10 * 1024 * 1024, backupCount=5
64
86
  )
65
87
  file_handler.setLevel(log_level)
66
- file_formatter = logging.Formatter(
67
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
68
- )
88
+ file_formatter = logging.Formatter(log_format)
69
89
  file_handler.setFormatter(file_formatter)
70
90
  self.logger.addHandler(file_handler)
71
91
  self.enable_opentelemetry()
@@ -86,8 +106,5 @@ class DatatailrLogger:
86
106
  from opentelemetry.instrumentation.logging import LoggingInstrumentor # type: ignore
87
107
 
88
108
  LoggingInstrumentor().instrument(set_logging_format=True)
89
- self.logger.debug("OpenTelemetry logging instrumentation enabled.")
90
109
  except ImportError:
91
- self.logger.debug(
92
- "OpenTelemetry is not installed. Proceeding without instrumentation."
93
- )
110
+ pass # OpenTelemetry is not installed, skip instrumentation
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # *************************************************************************
4
+ #
5
+ # Copyright (c) 2025 - Datatailr Inc.
6
+ # All Rights Reserved.
7
+ #
8
+ # This file is part of Datatailr and subject to the terms and conditions
9
+ # defined in 'LICENSE.txt'. Unauthorized copying and/or distribution
10
+ # of this file, in parts or full, via any medium is strictly prohibited.
11
+ # *************************************************************************
12
+
13
+
14
+ # The purpose of this script is to be the entrypoint for all jobs running on datatailr.
15
+ # The main functions of the script are:
16
+ # 1. Create a linux user and group for the job.
17
+ # 2. Set the environment variables for the job.
18
+ # 3. Run the job in a separate process, as the newly created user and pass all relevant environment variables.
19
+ # There are muliple environment variables which are required for the job to run.
20
+ # Some of them are necessary for the setup stage, which is executed directly in this script as the linux root user.
21
+ # Others are passed to the job script, which is executed in a separate process with only the users' privileges and not as a root user.
22
+ #
23
+ # Setup environment variables:
24
+ # DATATAILR_USER - the user under which the job will run.
25
+ # DATATAILR_GROUP - the group under which the job will run.
26
+ # DATATAILR_UID - the user ID of the user as it is defined in the system.
27
+ # DATATAILR_GID - the group ID of the group as it is defined in the system.
28
+ # DATATAILR_JOB_TYPE - the type of job to run. (batch\service\app\excel\IDE)
29
+ # Job environment variables (not all are always relevant, depending on the job type):
30
+ # DATATAILR_JOB_ARGUMENT_MAPPING - a JSON string mapping job argument names to their
31
+ # DATATAILR_BATCH_RUN_ID - the unique identifier for the batch run.
32
+ # DATATAILR_BATCH_ID - the unique identifier for the batch.
33
+ # DATATAILR_JOB_ID - the unique identifier for the job.
34
+
35
+
36
+ import os
37
+ from typing import Tuple
38
+ from datatailr.logging import DatatailrLogger
39
+ from datatailr.dt_json import encode_json
40
+
41
+
42
+ logger = DatatailrLogger(os.path.abspath(__file__)).get_logger()
43
+
44
+
45
+ def get_env_var(name: str, default: str | None = None) -> str:
46
+ """
47
+ Get an environment variable.
48
+ If the variable is not set, raise an error.
49
+ """
50
+ if name not in os.environ:
51
+ if default is not None:
52
+ return default
53
+ logger.error(f"Environment variable '{name}' is not set.")
54
+ raise ValueError(f"Environment variable '{name}' is not set.")
55
+ return os.environ[name]
56
+
57
+
58
+ def create_user_and_group() -> Tuple[str, str]:
59
+ """
60
+ Create a user and group for the job.
61
+ The user and group names are taken from the environment variables DATATAILR_USER and DATATAILR_GROUP.
62
+ The group and user are created with the same uid and gid as passed in the environment variables DATATAILR_UID and DATATAILR_GID.
63
+ If the user or group already exists, do nothing.
64
+ """
65
+ user = get_env_var("DATATAILR_USER")
66
+ group = get_env_var("DATATAILR_GROUP")
67
+ uid = get_env_var("DATATAILR_UID")
68
+ gid = get_env_var("DATATAILR_GID")
69
+
70
+ # Create group if it does not exist
71
+ os.system(f"getent group {group} || groupadd {group} -g {gid} -o")
72
+
73
+ # Create user if it does not exist
74
+ os.system(
75
+ f"getent passwd {user} || useradd -g {group} -s /bin/bash -m {user} -u {uid} -o"
76
+ )
77
+ return user, group
78
+
79
+
80
+ def run_command_as_user(command: str, user: str, env_vars: dict):
81
+ """
82
+ Run a command as a specific user with the given environment variables.
83
+ """
84
+ env_vars.update({"PATH": get_env_var("PATH")})
85
+ env_vars_str = " ".join(f"{key}='{value}'" for key, value in env_vars.items())
86
+ full_command = f"sudo -u {user} {env_vars_str} {command}"
87
+ logger.debug(f"Running command: {full_command}")
88
+ os.system(full_command)
89
+
90
+
91
+ def main():
92
+ user, _ = create_user_and_group()
93
+ job_type = get_env_var("DATATAILR_JOB_TYPE")
94
+
95
+ job_name = get_env_var("DATATAILR_JOB_NAME")
96
+ job_id = get_env_var("DATATAILR_JOB_ID")
97
+ entrypoint = get_env_var("DATATAILR_ENTRYPOINT")
98
+
99
+ if job_type == "batch":
100
+ run_id = get_env_var("DATATAILR_BATCH_RUN_ID")
101
+ batch_id = get_env_var("DATATAILR_BATCH_ID")
102
+ job_argument_mapping = get_env_var(
103
+ "DATATAILR_JOB_ARGUMENT_MAPPING", encode_json({})
104
+ )
105
+ env = {
106
+ "DATATAILR_BATCH_RUN_ID": run_id,
107
+ "DATATAILR_BATCH_ID": batch_id,
108
+ "DATATAILR_JOB_ID": job_id,
109
+ "DATATAILR_BATCH_ENTRYPOINT": entrypoint,
110
+ "DATATAILR_JOB_ARGUMENT_MAPPING": job_argument_mapping,
111
+ }
112
+ run_command_as_user("datatailr_run_batch", user, env)
113
+ elif job_type == "service":
114
+ env = {
115
+ "DATATAILR_JOB_NAME": job_name,
116
+ "DATATAILR_JOB_ID": job_id,
117
+ "DATATAILR_ENTRYPOINT": entrypoint,
118
+ }
119
+ run_command_as_user("datatailr_run_service", user, env)
120
+ elif job_type == "app":
121
+ env = {
122
+ "DATATAILR_JOB_NAME": job_name,
123
+ "DATATAILR_JOB_ID": job_id,
124
+ "DATATAILR_ENTRYPOINT": entrypoint,
125
+ }
126
+ run_command_as_user("datatailr_run_app", user, env)
127
+ elif job_type == "excel":
128
+ env = {
129
+ "DATATAILR_JOB_NAME": job_name,
130
+ "DATATAILR_JOB_ID": job_id,
131
+ "DATATAILR_ENTRYPOINT": entrypoint,
132
+ }
133
+ run_command_as_user("datatailr_run_excel", user, env)
134
+ elif job_type == "IDE":
135
+ pass
136
+ else:
137
+ raise ValueError(f"Unknown job type: {job_type}")
138
+
139
+
140
+ if __name__ == "__main__":
141
+ try:
142
+ logger.debug("Starting job execution...")
143
+ main()
144
+ logger.debug("Job executed successfully.")
145
+ except Exception as e:
146
+ logger.error(f"Error during job execution: {e}")
147
+ raise