automizor 0.3.1__py3-none-any.whl → 0.4.1__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.
automizor/__init__.py CHANGED
@@ -1 +1 @@
1
- version = "0.3.1"
1
+ version = "0.4.1"
@@ -0,0 +1,69 @@
1
+ from requests import Response
2
+
3
+
4
+ class AutomizorError(Exception):
5
+ def __init__(self, message, error=None):
6
+ if error:
7
+ message = f"{message}: {error}"
8
+ super().__init__(message)
9
+
10
+ def __str__(self):
11
+ return f"{self.args[0]}"
12
+
13
+ @classmethod
14
+ def from_response(cls, response: Response, message: str):
15
+ _STATUS_EXCEPTION_MAP = {
16
+ 400: InvalidRequest,
17
+ 401: Unauthorized,
18
+ 403: Forbidden,
19
+ 404: NotFound,
20
+ 429: RateLimitExceeded,
21
+ 500: InternalServerError,
22
+ 502: BadGateway,
23
+ 503: ServiceUnavailable,
24
+ }
25
+
26
+ try:
27
+ error = dict(response.json()).get("detail", "Unknown error.")
28
+ except Exception: # pylint: disable=broad-except
29
+ error = response.text
30
+
31
+ return _STATUS_EXCEPTION_MAP.get(response.status_code, UnexpectedError)(
32
+ message, error
33
+ )
34
+
35
+
36
+ class BadGateway(AutomizorError):
37
+ pass
38
+
39
+
40
+ class Forbidden(AutomizorError):
41
+ pass
42
+
43
+
44
+ class InternalServerError(AutomizorError):
45
+ pass
46
+
47
+
48
+ class InvalidRequest(AutomizorError):
49
+ pass
50
+
51
+
52
+ class NotFound(AutomizorError):
53
+ pass
54
+
55
+
56
+ class RateLimitExceeded(AutomizorError):
57
+ pass
58
+
59
+
60
+ class ServiceUnavailable(AutomizorError):
61
+ pass
62
+
63
+
64
+ class Unauthorized(AutomizorError):
65
+ pass
66
+
67
+
68
+ class UnexpectedError(AutomizorError):
69
+ pass
automizor/job/__init__.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from functools import lru_cache
2
2
 
3
- from ._exceptions import AutomizorJobError
4
3
  from ._job import JSON
5
4
 
6
5
 
@@ -44,7 +43,6 @@ def set_result(name: str, value: JSON):
44
43
 
45
44
 
46
45
  __all__ = [
47
- "AutomizorJobError",
48
46
  "get_context",
49
47
  "set_result",
50
48
  ]
automizor/job/_job.py CHANGED
@@ -4,7 +4,8 @@ from typing import Dict, List, Union
4
4
 
5
5
  import requests
6
6
 
7
- from ._exceptions import AutomizorJobError
7
+ from automizor.exceptions import AutomizorError
8
+ from automizor.utils import get_api_config, get_headers
8
9
 
9
10
  JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
10
11
 
@@ -30,8 +31,7 @@ class Job:
30
31
  To use this class effectively, ensure that the following environment variables are
31
32
  set in your environment:
32
33
 
33
- - ``AUTOMIZOR_API_HOST``: The URL of the `Automizor API` host.
34
- - ``AUTOMIZOR_API_TOKEN``: The authentication token for API access.
34
+ - ``AUTOMIZOR_AGENT_TOKEN``: The token for authenticating against the `Automizor API`.
35
35
  - ``AUTOMIZOR_CONTEXT_FILE``: The path to a local file containing job context, if used.
36
36
  - ``AUTOMIZOR_JOB_ID``: The identifier for the current job, used when fetching context via API.
37
37
 
@@ -50,18 +50,12 @@ class Job:
50
50
  """
51
51
 
52
52
  def __init__(self):
53
- self._api_host = os.getenv("AUTOMIZOR_API_HOST")
54
- self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
55
- self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE")
56
- self._job_id = os.getenv("AUTOMIZOR_JOB_ID")
53
+ self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE", None)
54
+ self._job_id = os.getenv("AUTOMIZOR_JOB_ID", None)
57
55
 
56
+ self.url, self.token = get_api_config()
58
57
  self.session = requests.Session()
59
- self.session.headers.update(
60
- {
61
- "Authorization": f"Token {self._api_token}",
62
- "Content-Type": "application/json",
63
- }
64
- )
58
+ self.session.headers.update(get_headers(self.token))
65
59
 
66
60
  def get_context(self) -> dict:
67
61
  """
@@ -73,8 +67,7 @@ class Job:
73
67
  This is useful in environments where direct access to the `Automizor API` is
74
68
  not possible or preferred.
75
69
  2. The `Automizor API`, using the job ID (`AUTOMIZOR_JOB_ID`) to fetch the specific
76
- job context. This requires valid API host (`AUTOMIZOR_API_HOST`) and token
77
- (`AUTOMIZOR_API_TOKEN`) settings.
70
+ job context.
78
71
 
79
72
  Returns:
80
73
  A dictionary containing the job context.
@@ -118,17 +111,21 @@ class Job:
118
111
  data[name] = value
119
112
 
120
113
  with open(file_path, "w", encoding="utf-8") as file:
121
- json.dump(data, file, ensure_ascii=False, indent=4)
114
+ json.dump(data, file, ensure_ascii=False)
122
115
 
123
116
  def _read_file_context(self) -> dict:
124
117
  with open(self._context_file, "r", encoding="utf-8") as file:
125
118
  return json.load(file)
126
119
 
127
120
  def _read_job_context(self) -> dict:
128
- url = f"https://{self._api_host}/api/v1/rpa/job/{self._job_id}/"
121
+ url = f"https://{self.url}/api/v1/rpa/job/{self._job_id}/"
129
122
  try:
130
123
  response = self.session.get(url, timeout=10)
131
124
  response.raise_for_status()
132
125
  return response.json().get("context", {})
126
+ except requests.HTTPError as exc:
127
+ raise AutomizorError.from_response(
128
+ exc.response, "Failed to get job context"
129
+ ) from exc
133
130
  except Exception as exc:
134
- raise AutomizorJobError(f"Failed to get job context: {exc}") from exc
131
+ raise AutomizorError("Failed to get job context") from exc
@@ -0,0 +1,95 @@
1
+ from functools import lru_cache
2
+
3
+ from ._log import JSON
4
+
5
+
6
+ @lru_cache
7
+ def _get_log():
8
+ from ._log import Log
9
+
10
+ return Log()
11
+
12
+
13
+ def debug(msg: JSON):
14
+ """
15
+ Writes a debug log message with a level of "DEBUG".
16
+
17
+ Parameters:
18
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
19
+ list, or None.
20
+ """
21
+
22
+ _get_log().write_log("DEBUG", msg)
23
+
24
+
25
+ def info(msg: JSON):
26
+ """
27
+ Writes an info log message with a level of "INFO".
28
+
29
+ Parameters:
30
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
31
+ list, or None.
32
+ """
33
+
34
+ _get_log().write_log("INFO", msg)
35
+
36
+
37
+ def warning(msg: JSON):
38
+ """
39
+ Writes a warning log message with a level of "WARNING".
40
+
41
+ Parameters:
42
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
43
+ list, or None.
44
+ """
45
+
46
+ _get_log().write_log("WARNING", msg)
47
+
48
+
49
+ def error(msg: JSON):
50
+ """
51
+ Writes an error log message with a level of "ERROR".
52
+
53
+ Parameters:
54
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
55
+ list, or None.
56
+ """
57
+
58
+ _get_log().write_log("ERROR", msg)
59
+
60
+
61
+ def critical(msg: JSON):
62
+ """
63
+ Writes a critical log message with a level of "CRITICAL".
64
+
65
+ Parameters:
66
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
67
+ list, or None.
68
+ """
69
+
70
+ _get_log().write_log("CRITICAL", msg)
71
+
72
+
73
+ def set_level(level: str):
74
+ """
75
+ Set the log level for filtering log messages.
76
+
77
+ Parameters:
78
+ level (str): The log level to set. Valid log levels are "DEBUG", "INFO",
79
+ "WARNING", "ERROR", and "CRITICAL".
80
+
81
+ Raises:
82
+ ValueError: If an invalid log level is provided.
83
+ """
84
+
85
+ _get_log().set_level(level)
86
+
87
+
88
+ __all__ = [
89
+ "debug",
90
+ "info",
91
+ "warning",
92
+ "error",
93
+ "critical",
94
+ "set_level",
95
+ ]
automizor/log/_log.py ADDED
@@ -0,0 +1,91 @@
1
+ import json
2
+ import os
3
+ from typing import Dict, List, Union
4
+
5
+ LOG_LEVELS = {
6
+ "DEBUG": 1,
7
+ "INFO": 2,
8
+ "WARNING": 3,
9
+ "ERROR": 4,
10
+ "CRITICAL": 5,
11
+ }
12
+
13
+ JSON = Union[str, int, float, bool, None, Dict[str, "JSON"], List["JSON"]]
14
+
15
+
16
+ class Log:
17
+ """
18
+ `Log` is a class that facilitates logging messages to a local file, which can be
19
+ useful for debugging and monitoring purposes. It provides a simple interface for
20
+ writing log messages with different levels, such as "info", "warning", "error", etc.
21
+
22
+ The log messages are stored in a JSON file, which can be easily parsed and analyzed
23
+ later. This class does not provide any log rotation or cleanup mechanisms, so it is
24
+ recommended to manage log files manually or use external log management tools.
25
+
26
+ Example usage:
27
+
28
+ .. code-block:: python
29
+
30
+ from automizor import log
31
+
32
+ # Set log level to INFO
33
+ log.set_level("INFO")
34
+
35
+ # Write a log message
36
+ log.info({"key": "value"})
37
+ """
38
+
39
+ def __init__(self):
40
+ self.level = "INFO"
41
+
42
+ def set_level(self, level: str):
43
+ """
44
+ Set the log level for filtering log messages.
45
+
46
+ Parameters:
47
+ level (str): The log level to set. Valid log levels are "DEBUG", "INFO",
48
+ "WARNING", "ERROR", and "CRITICAL".
49
+
50
+ Raises:
51
+ ValueError: If an invalid log level is provided.
52
+ """
53
+
54
+ if level not in LOG_LEVELS:
55
+ raise ValueError(f"Invalid log level: {level}")
56
+
57
+ self.level = level
58
+
59
+ def write_log(self, level: str, msg: JSON):
60
+ """
61
+ Write a log message with the specified log level.
62
+
63
+ Parameters:
64
+ level (str): The log level of the message. Valid log levels are "DEBUG", "INFO",
65
+ "WARNING", "ERROR", and "CRITICAL".
66
+ msg (JSON): The log message to write. This can be a string, number, boolean, dictionary,
67
+ list, or None.
68
+
69
+ Raises:
70
+ ValueError: If an invalid log level is provided.
71
+ """
72
+
73
+ if level not in LOG_LEVELS:
74
+ raise ValueError(f"Invalid log level: {level}")
75
+
76
+ if LOG_LEVELS[level] < LOG_LEVELS[self.level]:
77
+ return
78
+
79
+ data = []
80
+ file_path = "output/log.json"
81
+ try:
82
+ if os.path.exists(file_path):
83
+ with open(file_path, "r", encoding="utf-8") as file:
84
+ data = json.load(file)
85
+ except json.JSONDecodeError:
86
+ pass
87
+
88
+ data.append({"level": level, "msg": msg})
89
+
90
+ with open(file_path, "w", encoding="utf-8") as file:
91
+ json.dump(data, file, ensure_ascii=False)
@@ -1,6 +1,9 @@
1
+ import json
2
+ import mimetypes
1
3
  from functools import lru_cache
4
+ from pathlib import Path
5
+ from typing import List
2
6
 
3
- from ._exceptions import AutomizorStorageError
4
7
  from ._storage import JSON
5
8
 
6
9
 
@@ -11,6 +14,30 @@ def _get_storage():
11
14
  return Storage()
12
15
 
13
16
 
17
+ def list_assets() -> List[str]:
18
+ """
19
+ Retrieves a list of all asset names.
20
+
21
+ Returns:
22
+ A list of all asset names.
23
+ """
24
+
25
+ storage = _get_storage()
26
+ return storage.list_assets()
27
+
28
+
29
+ def delete_asset(name: str):
30
+ """
31
+ Deletes the specified asset.
32
+
33
+ Parameters:
34
+ name: The name identifier of the asset to delete.
35
+ """
36
+
37
+ storage = _get_storage()
38
+ storage.delete_asset(name)
39
+
40
+
14
41
  def get_bytes(name: str) -> bytes:
15
42
  """
16
43
  Retrieves the specified asset as raw bytes.
@@ -72,10 +99,82 @@ def get_text(name: str) -> str:
72
99
  return storage.get_text(name)
73
100
 
74
101
 
102
+ def set_bytes(name: str, data: bytes, content_type="application/octet-stream"):
103
+ """
104
+ Uploads raw bytes as an asset.
105
+
106
+ Parameters:
107
+ name: The name identifier of the asset to upload.
108
+ data: The raw byte content to upload.
109
+ content_type: The MIME type of the asset.
110
+ """
111
+
112
+ storage = _get_storage()
113
+ storage.set_bytes(name, data, content_type)
114
+
115
+
116
+ def set_file(name: str, path: str, content_type: str = None):
117
+ """
118
+ Uploads a file as an asset.
119
+
120
+ Parameters:
121
+ name: The name identifier of the asset to upload.
122
+ path: The filesystem path of the file to upload.
123
+ content_type: The MIME type of the asset.
124
+ """
125
+
126
+ content = Path(path).read_bytes()
127
+ if content_type is None:
128
+ content_type, _ = mimetypes.guess_type(path)
129
+ if content_type is None:
130
+ content_type = "application/octet-stream"
131
+
132
+ storage = _get_storage()
133
+ storage.set_bytes(name, content, content_type)
134
+
135
+
136
+ def set_json(name: str, value: JSON, **kwargs):
137
+ """
138
+ Uploads JSON data as an asset.
139
+
140
+ Parameters:
141
+ name: The name identifier of the asset to upload.
142
+ value: The JSON data to upload.
143
+ kwargs: Additional keyword arguments to pass to json.dumps.
144
+ """
145
+
146
+ content = json.dumps(value, **kwargs).encode("utf-8")
147
+ content_type = "application/json"
148
+
149
+ storage = _get_storage()
150
+ storage.set_bytes(name, content, content_type)
151
+
152
+
153
+ def set_text(name: str, text: str):
154
+ """
155
+ Uploads text content as an asset.
156
+
157
+ Parameters:
158
+ name: The name identifier of the asset to upload.
159
+ text: The text content to upload.
160
+ """
161
+
162
+ content = text.encode("utf-8")
163
+ content_type = "text/plain"
164
+
165
+ storage = _get_storage()
166
+ storage.set_bytes(name, content, content_type)
167
+
168
+
75
169
  __all__ = [
76
- "AutomizorStorageError",
170
+ "list_assets",
171
+ "delete_asset",
77
172
  "get_bytes",
78
173
  "get_file",
79
174
  "get_json",
80
175
  "get_text",
176
+ "set_bytes",
177
+ "set_file",
178
+ "set_json",
179
+ "set_text",
81
180
  ]