automizor 0.3.0__py3-none-any.whl → 0.4.0__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.
@@ -0,0 +1,69 @@
1
+ from dataclasses import dataclass, fields
2
+ from typing import Any, Dict
3
+
4
+
5
+ def ignore_extra_fields(cls):
6
+ original_post_init = getattr(cls, "__post_init__", None)
7
+
8
+ def __init__(self, **kwargs):
9
+ cls_fields = {field.name: field for field in fields(cls)}
10
+ for name, value in kwargs.items():
11
+ if name in cls_fields:
12
+ setattr(self, name, value)
13
+ if original_post_init:
14
+ original_post_init(self)
15
+
16
+ setattr(cls, "__init__", __init__)
17
+ return cls
18
+
19
+
20
+ @ignore_extra_fields
21
+ @dataclass
22
+ class SecretContainer:
23
+ """
24
+ Represents a secret, comprising a name and its associated values.
25
+
26
+ Attributes:
27
+ description: A description of the secret.
28
+ name: The name of the secret.
29
+ value: The secret's values, stored in a dictionary as key-value pairs.
30
+ """
31
+
32
+ name: str
33
+ value: Dict[str, Any]
34
+ description: str = ""
35
+
36
+ def get(self, key, default=None):
37
+ """Return the value for key if key is in the dictionary, else default."""
38
+ return self.value.get(key, default)
39
+
40
+ def items(self):
41
+ """Secret.items() -> a set-like object providing a view on secret's items."""
42
+ return self.value.items()
43
+
44
+ def keys(self):
45
+ """Secret.keys() -> a set-like object providing a view on secret's keys."""
46
+ return self.value.keys()
47
+
48
+ def update(self, pairs: Dict[str, Any]) -> None:
49
+ """Update the secret's dictionary with the key-value pairs from pairs."""
50
+ self.value.update(pairs)
51
+
52
+ def __getitem__(self, key):
53
+ return self.value[key]
54
+
55
+ def __setitem__(self, key, value):
56
+ self.value[key] = value
57
+
58
+ def __contains__(self, key):
59
+ return key in self.value
60
+
61
+ def __iter__(self):
62
+ return iter(self.value)
63
+
64
+ def __len__(self):
65
+ return len(self.value)
66
+
67
+ def __repr__(self):
68
+ keys = ", ".join(self.keys())
69
+ return f"Secret(name={self.name}, keys=[{keys}])"
automizor/vault/_vault.py CHANGED
@@ -1,42 +1,26 @@
1
- import json
2
- import os
3
1
  from dataclasses import asdict
4
2
 
5
3
  import requests
6
4
 
7
- from ._exceptions import AutomizorVaultError
8
- from ._secret import Secret
5
+ from automizor.exceptions import AutomizorError, NotFound
6
+ from automizor.utils import get_api_config, get_headers
7
+
8
+ from ._container import SecretContainer
9
9
 
10
10
 
11
11
  class Vault:
12
12
  """
13
13
  `Vault` is a secure storage class within the `Automizor Platform` for managing
14
14
  secrets such as API keys, passwords, and other sensitive information. It offers
15
- functionality to securely retrieve and update secrets, either through direct
16
- interaction with the `Automizor API` or via a local secrets file, depending on
17
- the operational environment and configuration.
15
+ functionality to securely retrieve and update secrets through direct interaction
16
+ with the `Automizor API`.
18
17
 
19
18
  Configuration for accessing and manipulating these secrets is driven by environment
20
19
  variables, which are essential for specifying the API's host and token for
21
- authentication purposes, alongside the location of a local secrets file, should
22
- local storage be preferred over API interaction.
20
+ authentication purposes.
23
21
 
24
22
  Environment variables requisite for operation include:
25
- - ``AUTOMIZOR_API_HOST``: The host URL for the `Automizor API`.
26
- - ``AUTOMIZOR_API_TOKEN``: The token for authenticate against the `Automizor API`.
27
- - ``AUTOMIZOR_SECRET_FILE``: Optionally specifies the path to a local file where
28
- secrets are stored, enabling operations in environments where direct API access
29
- may be restricted or unavailable.
30
-
31
- Example of a local secret file:
32
-
33
- .. code-block:: json
34
-
35
- {
36
- "my_secret_name": {
37
- "key": "value"
38
- }
39
- }
23
+ - ``AUTOMIZOR_AGENT_TOKEN``: The token for authenticating against the `Automizor API`.
40
24
 
41
25
  Example usage:
42
26
 
@@ -44,33 +28,48 @@ class Vault:
44
28
 
45
29
  from automizor import vault
46
30
 
31
+ # Create a new secret
32
+ vault.create_secret(name="MySecret", value={"username": "admin", "password": "*****"})
33
+
47
34
  # Retrieve a secret by its name
48
- secret = vault.get_secret("my_secret_name")
49
- print(secret["key"]) # Output: "value"
35
+ secret = vault.get_secret("MySecret")
36
+ print(secret.get("username")) # Output: "admin"
37
+ print(secret.get("password")) # Output: "*****"
50
38
 
51
39
  # Update a existing secret
52
- secret = vault.get_secret("my_secret_name")
53
- secret["new_key"] = "new_value"
40
+ secret = vault.get_secret("MySecret")
41
+ secret.update({"username": "user"})
54
42
  vault.set_secret(secret)
55
43
  """
56
44
 
57
45
  def __init__(self):
58
- self._api_host = os.getenv("AUTOMIZOR_API_HOST")
59
- self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
60
- self._secret_file = os.getenv("AUTOMIZOR_SECRET_FILE")
61
-
46
+ self.url, self.token = get_api_config()
62
47
  self.session = requests.Session()
63
- self.session.headers.update(
64
- {
65
- "Authorization": f"Token {self._api_token}",
66
- "Content-Type": "application/json",
67
- }
68
- )
69
-
70
- def get_secret(self, name) -> Secret:
48
+ self.session.headers.update(get_headers(self.token))
49
+
50
+ def create_secret(self, secret: SecretContainer) -> SecretContainer:
51
+ """
52
+ Creates a new secret. Stores the secret in the `Automizor API`.
53
+ If the secret already exists, it will be updated.
54
+
55
+ Args:
56
+ secret: The secret to create.
57
+
58
+ Returns:
59
+ The created secret.
60
+
61
+ Raises:
62
+ AutomizorVaultError: If creating the secret fails.
71
63
  """
72
- Retrieves a secret by its name. Fetches from a local file or queries the
73
- `Automizor API`, based on configuration.
64
+
65
+ try:
66
+ return self._update_secret(secret)
67
+ except NotFound:
68
+ return self._create_secret(secret)
69
+
70
+ def get_secret(self, name) -> SecretContainer:
71
+ """
72
+ Retrieves a secret by its name. Fetches from the `Automizor API`.
74
73
 
75
74
  Args:
76
75
  name: The name of the secret to retrieve.
@@ -82,14 +81,11 @@ class Vault:
82
81
  AutomizorVaultError: If retrieving the secret fails.
83
82
  """
84
83
 
85
- if self._secret_file:
86
- return self._read_file_secret(name)
87
- return self._read_vault_secret(name)
84
+ return self._get_secret(name)
88
85
 
89
- def set_secret(self, secret: Secret) -> Secret:
86
+ def set_secret(self, secret: SecretContainer) -> SecretContainer:
90
87
  """
91
- Updates an existing secret. Updates to a local file or to the
92
- `Automizor API`, based on configuration.
88
+ Updates an existing secret. Updates to the `Automizor API`.
93
89
 
94
90
  Args:
95
91
  secret: The secret to update.
@@ -101,39 +97,43 @@ class Vault:
101
97
  AutomizorVaultError: If updating the secret fails.
102
98
  """
103
99
 
104
- if self._secret_file:
105
- return self._write_file_secret(secret)
106
- return self._write_vault_secret(secret)
100
+ return self._update_secret(secret)
107
101
 
108
- def _read_file_secret(self, name: str) -> Secret:
109
- with open(self._secret_file, "r", encoding="utf-8") as file:
110
- secrets = json.load(file)
111
- value = secrets.get(name, {})
112
- return Secret(name=name, value=value)
102
+ def _create_secret(self, secret: SecretContainer) -> SecretContainer:
103
+ url = f"https://{self.url}/api/v1/vault/secret/"
104
+ try:
105
+ response = self.session.post(url, timeout=10, json=asdict(secret))
106
+ response.raise_for_status()
107
+ return SecretContainer(**response.json())
108
+ except requests.HTTPError as exc:
109
+ raise AutomizorError.from_response(
110
+ exc.response, "Failed to create secret"
111
+ ) from exc
112
+ except Exception as exc:
113
+ raise AutomizorError("Failed to create secret") from exc
113
114
 
114
- def _read_vault_secret(self, name: str) -> Secret:
115
- url = f"https://{self._api_host}/api/v1/vault/secret/{name}/"
115
+ def _get_secret(self, name: str) -> SecretContainer:
116
+ url = f"https://{self.url}/api/v1/vault/secret/{name}/"
116
117
  try:
117
118
  response = self.session.get(url, timeout=10)
118
119
  response.raise_for_status()
119
- return Secret(**response.json())
120
+ return SecretContainer(**response.json())
121
+ except requests.HTTPError as exc:
122
+ raise AutomizorError.from_response(
123
+ exc.response, "Failed to get secret"
124
+ ) from exc
120
125
  except Exception as exc:
121
- raise AutomizorVaultError(f"Failed to get secret: {exc}") from exc
122
-
123
- def _write_file_secret(self, secret: Secret):
124
- with open(self._secret_file, "r+", encoding="utf-8") as file:
125
- secrets = json.load(file)
126
- secrets[secret.name] = secret.value
127
- file.seek(0)
128
- file.write(json.dumps(secrets, indent=4))
129
- file.truncate()
130
- return secret
131
-
132
- def _write_vault_secret(self, secret: Secret) -> Secret:
133
- url = f"https://{self._api_host}/api/v1/vault/secret/{secret.name}/"
126
+ raise AutomizorError("Failed to get secret") from exc
127
+
128
+ def _update_secret(self, secret: SecretContainer) -> SecretContainer:
129
+ url = f"https://{self.url}/api/v1/vault/secret/{secret.name}/"
134
130
  try:
135
131
  response = self.session.put(url, timeout=10, json=asdict(secret))
136
132
  response.raise_for_status()
137
- return Secret(**response.json())
133
+ return SecretContainer(**response.json())
134
+ except requests.HTTPError as exc:
135
+ raise AutomizorError.from_response(
136
+ exc.response, "Failed to update secret"
137
+ ) from exc
138
138
  except Exception as exc:
139
- raise AutomizorVaultError(f"Failed to set secret: {exc}") from exc
139
+ raise AutomizorError("Failed to update secret") from exc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: automizor
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Python Automizor framework
5
5
  Home-page: https://github.com/automizor/automizor
6
6
  Author: Christian Fischer
@@ -0,0 +1,15 @@
1
+ automizor/__init__.py,sha256=yhiWOz0HoJGRRI9-JQ2eh_0AbByy-6psK08-kpTSHJw,18
2
+ automizor/exceptions.py,sha256=P5imySIOtG3ZIk2kh41Yod4RnlgTj7Vf0P3M-RuxQJs,1382
3
+ automizor/job/__init__.py,sha256=DNRuT6cyPQBaPRG4vNalCmEvcJQl-73b5ZDFOfNOwIg,1019
4
+ automizor/job/_job.py,sha256=62T_eMEqsJlxhlQ8_3nupTvvwsEysgx5JZAz7ui1WLo,5245
5
+ automizor/storage/__init__.py,sha256=DUVVvetBgsrF69mfiM9iu1PvSBjFPLxrgH1o18mvReA,4000
6
+ automizor/storage/_storage.py,sha256=rKWxaCA_kFr5cW_RrHqL4uEVzNqdWjTQAOXFTfIxlfY,11021
7
+ automizor/utils/__init__.py,sha256=2trRoR5lljYKbYdxmioSlvzajjQM0Wnoq3bvF9lEZ6w,811
8
+ automizor/vault/__init__.py,sha256=UjRiW3J0R9ABXc1gXIPyS3cqNCwMWxx0l-C0PsIg7R0,1699
9
+ automizor/vault/_container.py,sha256=TXwNhjK-qrfu_5iDJm1J0Kkuyq22dLdES1DqjAAKnWA,1953
10
+ automizor/vault/_vault.py,sha256=uRsjOjzsstZpYwJoHNg_cpv803Dzo4T2oF6hwiG3Eww,4688
11
+ automizor-0.4.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
12
+ automizor-0.4.0.dist-info/METADATA,sha256=Ky_rwJrB2JUutCKAYo9Rzkwo_j6UPyQaS8HRORjr6O8,661
13
+ automizor-0.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
14
+ automizor-0.4.0.dist-info/top_level.txt,sha256=gScDy4I3tP6BMYAsTAlBXrxVh3E00zV0UioxwXJOI3Y,10
15
+ automizor-0.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +0,0 @@
1
- class AutomizorJobError(RuntimeError):
2
- """Exception raised for errors encountered while interacting with the Job."""
automizor/job.py DELETED
@@ -1,132 +0,0 @@
1
- import json
2
- import os
3
- from typing import Any, Dict, List, Union
4
-
5
- import requests
6
-
7
- JSONType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]
8
-
9
-
10
- class AutomizorJobError(RuntimeError):
11
- """Exception raised for errors encountered while interacting with the Job."""
12
-
13
-
14
- class Job:
15
- """
16
- Represents a job in the `Automizor Platform`, managing the retrieval and storage of job
17
- context and results.
18
-
19
- This class provides functionality to interact with the `Automizor API` or local files to
20
- obtain job context and persist job results.
21
-
22
- For testing purposes, you may want to set the environment variables in your local environment.
23
- The required variables are:
24
-
25
- - ``AUTOMIZOR_API_HOST``: The host URL of the `Automizor API`
26
- - ``AUTOMIZOR_API_TOKEN``: The token used for authenticating with the `Automizor API`
27
- - ``AUTOMIZOR_JOB_ID``: The ID of the job from which to retrieve context
28
-
29
- Additionally, you can specify a local context file; in this case, you don't need to set the
30
- above environment variables:
31
-
32
- - ``AUTOMIZOR_CONTEXT_FILE``: The path to a local file containing the job context.
33
-
34
- Example of a local context file:
35
-
36
- .. code-block:: json
37
-
38
- {
39
- "key": "value"
40
- }
41
-
42
- Example usage:
43
-
44
- .. code-block:: python
45
-
46
- from automizor.job import Job
47
-
48
- job = Job()
49
-
50
- def read_context():
51
- context = job.get_job_context()
52
- print(context["key"]) # Output: "value"
53
-
54
- def save_result():
55
- job.set_job_result("result_key", "result_value")
56
-
57
- """
58
-
59
- def __init__(self):
60
- self._api_host = os.getenv("AUTOMIZOR_API_HOST")
61
- self._api_token = os.getenv("AUTOMIZOR_API_TOKEN")
62
- self._context_file = os.getenv("AUTOMIZOR_CONTEXT_FILE")
63
- self._job_id = os.getenv("AUTOMIZOR_JOB_ID")
64
-
65
- @property
66
- def headers(self) -> dict:
67
- """Headers for API requests, including Authorization and Content-Type."""
68
- return {
69
- "Authorization": f"Token {self._api_token}",
70
- "Content-Type": "application/json",
71
- }
72
-
73
- def get_job_context(self) -> dict:
74
- """
75
- Retrieves the job's context from either a local file or via an API call, based on
76
- the configuration.
77
-
78
- If a local context file is specified (via `AUTOMIZOR_CONTEXT_FILE`), it reads the context
79
- from the file. Otherwise, it fetches the context from the Automizor API using the job ID
80
- and API credentials.
81
-
82
- Returns:
83
- A dictionary with the job's context.
84
-
85
- Raises:
86
- AutomizorJobError: If retrieving the job context fails.
87
- """
88
-
89
- if self._context_file:
90
- return self._read_file_context()
91
- return self._read_job_context()
92
-
93
- def set_job_result(self, name: str, value: JSONType):
94
- """
95
- Saves a job result into a JSON file (`output/result.json`).
96
-
97
- Updates the file with the new result, creating or overwriting the file as necessary.
98
- If the file exists and contains data, it merges the new result with the existing data.
99
-
100
- Parameters:
101
- name (str): The key under which to store the result.
102
- value (JSONType): The result value, must be JSON serializable.
103
-
104
- Note: Errors during file operations will raise unhandled exceptions.
105
- """
106
-
107
- data = {}
108
- file_path = "output/result.json"
109
- try:
110
- if os.path.exists(file_path):
111
- with open(file_path, "r", encoding="utf-8") as file:
112
- data = json.load(file)
113
- except json.JSONDecodeError:
114
- pass
115
-
116
- data[name] = value
117
-
118
- with open(file_path, "w", encoding="utf-8") as file:
119
- json.dump(data, file, ensure_ascii=False, indent=4)
120
-
121
- def _read_file_context(self) -> dict:
122
- with open(self._context_file, "r", encoding="utf-8") as file:
123
- return json.load(file)
124
-
125
- def _read_job_context(self) -> dict:
126
- url = f"https://{self._api_host}/api/v1/rpa/job/{self._job_id}/"
127
- try:
128
- response = requests.get(url, headers=self.headers, timeout=10)
129
- response.raise_for_status()
130
- return response.json().get("context", {})
131
- except Exception as exc:
132
- raise AutomizorJobError(f"Failed to get job context: {exc}") from exc
@@ -1,2 +0,0 @@
1
- class AutomizorStorageError(RuntimeError):
2
- """Exception raised for errors encountered while interacting with the Storage."""
automizor/storage.py DELETED
File without changes
@@ -1,2 +0,0 @@
1
- class AutomizorVaultError(RuntimeError):
2
- """Exception raised for errors encountered while interacting with the Vault."""
@@ -1,45 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass
5
- class Secret:
6
- """
7
- Represents a secret, comprising a name and its associated values.
8
-
9
- Attributes:
10
- name: The name of the secret.
11
- value: The secret's values, stored in a dictionary as key-value pairs.
12
- """
13
-
14
- name: str
15
- value: dict
16
-
17
- def get(self, key, default=None):
18
- """Return the value for key if key is in the dictionary, else default."""
19
- return self.value.get(key, default)
20
-
21
- def items(self):
22
- """secret.items() -> a set-like object providing a view on secret's items."""
23
- return self.value.items()
24
-
25
- def update(self, pairs: dict) -> None:
26
- self.value.update(pairs)
27
-
28
- def __getitem__(self, key):
29
- return self.value[key]
30
-
31
- def __setitem__(self, key, value):
32
- self.value[key] = value
33
-
34
- def __contains__(self, key):
35
- return key in self.value
36
-
37
- def __iter__(self):
38
- return iter(self.value)
39
-
40
- def __len__(self):
41
- return len(self.value)
42
-
43
- def __repr__(self):
44
- keys = ", ".join(self.value.keys())
45
- return f"Secret(name={self.name}, keys={keys})"