Skip to content

Commit c7a9890

Browse files
S-119017 For New Release API Client
* S-119017 For New APIClient * S-119017 For New APIClient * S-119017 For New APIClient * S-119017 For New APIClient * S-119017 For New APIClient * S-119017 For New APIClient
1 parent 97fbae9 commit c7a9890

File tree

321 files changed

+264
-227801
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

321 files changed

+264
-227801
lines changed

README.md

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,68 @@
1-
# Digital.ai Release SDK
1+
# Digital.ai Release Python SDK
22

3-
The Digital.ai Release Python SDK (digitalai-release-sdk) is a set of tools that developers can use to create container-based tasks.
3+
The **Digital.ai Release Python SDK** (`digitalai-release-sdk`) provides a set of tools for developers to create container-based integration with Digital.ai Release. It simplifies integration creation by offering built-in functions to interact with the execution environment.
4+
5+
## Features
6+
- Define custom tasks using the `BaseTask` abstract class.
7+
- Easily manage input and output properties.
8+
- Interact with the Digital.ai Release environment seamlessly.
9+
- Simplified API client for efficient communication with Release API.
410

5-
Developers can use the `BaseTask` abstract class as a starting point to define their custom tasks and take advantage of the other methods and attributes provided by the SDK to interact with the task execution environment.
611

712
## Installation
13+
Install the SDK using `pip`:
814

9-
```shell script
15+
```sh
1016
pip install digitalai-release-sdk
1117
```
12-
## Task Example: hello.py
18+
19+
## Getting Started
20+
21+
### Example Task: `hello.py`
22+
23+
The following example demonstrates how to create a simple task using the SDK:
1324

1425
```python
1526
from digitalai.release.integration import BaseTask
1627

1728
class Hello(BaseTask):
1829

1930
def execute(self) -> None:
20-
2131
# Get the name from the input
22-
name = self.input_properties['yourName']
32+
name = self.input_properties.get('yourName')
2333
if not name:
2434
raise ValueError("The 'yourName' field cannot be empty")
2535

26-
# Create greeting
36+
# Create greeting message
2737
greeting = f"Hello {name}"
2838

2939
# Add greeting to the task's comment section in the UI
3040
self.add_comment(greeting)
3141

32-
# Put greeting in the output of the task
42+
# Store greeting as an output property
3343
self.set_output_property('greeting', greeting)
44+
```
45+
46+
## Changelog
47+
### Version 25.1.0
48+
49+
#### 🚨 Breaking Changes
50+
- **Removed `get_default_api_client()`** from the `BaseTask` class.
51+
- **Removed `digitalai.release.v1` package**, which contained OpenAPI-generated stubs for Release API functions.
52+
- These stubs were difficult to use and had several non-functioning methods.
53+
- A new, simplified API client replaces them for better usability and reliability.
54+
- The removed package will be released as a separate library in the future.
55+
56+
#### ✨ New Features
57+
- **Introduced `get_release_api_client()`** in the `BaseTask` class as a replacement for `get_default_api_client()`.
58+
- **New `ReleaseAPIClient` class** for simplified API interactions.
59+
- Functions in `ReleaseAPIClient` take an **endpoint URL** and **body as a dictionary**, making API calls more intuitive and easier to work with.
60+
61+
#### 🔧 Changes & Improvements
62+
- **Updated minimum Python version requirement to 3.8**.
63+
- **Updated dependency versions** to enhance compatibility and security.
64+
- **Bundled `requests` library** to ensure seamless HTTP request handling.
3465

35-
```
66+
---
67+
**For more details, visit the [official documentation](https://docs.digital.ai/release/docs/category/python-sdk).**
3668

37-
## Documentation
38-
Read more about Digital.ai Release Python SDK [here](https://digital.ai/)

digitalai/release/integration/base_task.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@
33
from abc import ABC, abstractmethod
44
from typing import Any, Dict
55

6-
from digitalai.release.v1.configuration import Configuration
7-
8-
from digitalai.release.v1.api_client import ApiClient
9-
10-
from .input_context import AutomatedTaskAsUserContext, ReleaseContext
6+
from .input_context import AutomatedTaskAsUserContext
117
from .output_context import OutputContext
128
from .exceptions import AbortException
9+
from digitalai.release.release_api_client import ReleaseAPIClient
1310

1411
logger = logging.getLogger("Digitalai")
1512

@@ -18,6 +15,14 @@ class BaseTask(ABC):
1815
"""
1916
An abstract base class representing a task that can be executed.
2017
"""
18+
19+
def __init__(self):
20+
self.task_id = None
21+
self.release_context = None
22+
self.release_server_url = None
23+
self.input_properties = None
24+
self.output_context = None
25+
2126
def execute_task(self) -> None:
2227
"""
2328
Executes the task by calling the execute method. If an AbortException is raised during execution,
@@ -30,8 +35,8 @@ def execute_task(self) -> None:
3035
except AbortException:
3136
logger.debug("Abort requested")
3237
self.set_exit_code(1)
33-
sys.exit(1)
3438
self.set_error_message("Abort requested")
39+
sys.exit(1)
3540
except Exception as e:
3641
logger.error("Unexpected error occurred.", exc_info=True)
3742
self.set_exit_code(1)
@@ -130,21 +135,6 @@ def get_task_user(self) -> AutomatedTaskAsUserContext:
130135
"""
131136
return self.release_context.automated_task_as_user
132137

133-
def get_default_api_client(self) -> ApiClient:
134-
"""
135-
Returns an ApiClient object with default configuration based on the task.
136-
"""
137-
if not all([self.get_release_server_url(), self.get_task_user().username, self.get_task_user().password]):
138-
raise ValueError("Cannot connect to Release API without server URL, username, or password. "
139-
"Make sure that the 'Run as user' property is set on the release.")
140-
141-
configuration = Configuration(
142-
host=self.get_release_server_url(),
143-
username=self.get_task_user().username,
144-
password=self.get_task_user().password)
145-
146-
return ApiClient(configuration)
147-
148138
def get_release_id(self) -> str:
149139
"""
150140
Returns the Release ID of the task
@@ -157,5 +147,28 @@ def get_task_id(self) -> str:
157147
"""
158148
return self.task_id
159149

150+
def get_release_api_client(self) -> ReleaseAPIClient:
151+
"""
152+
Returns a ReleaseAPIClient object with default configuration based on the task.
153+
"""
154+
self._validate_api_credentials()
155+
return ReleaseAPIClient(self.get_release_server_url(),
156+
self.get_task_user().username,
157+
self.get_task_user().password)
158+
159+
def _validate_api_credentials(self) -> None:
160+
"""
161+
Validates that the necessary credentials are available for connecting to the Release API.
162+
"""
163+
if not all([
164+
self.get_release_server_url(),
165+
self.get_task_user().username,
166+
self.get_task_user().password
167+
]):
168+
raise ValueError(
169+
"Cannot connect to Release API without server URL, username, or password. "
170+
"Make sure that the 'Run as user' property is set on the release."
171+
)
172+
160173

161174

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import requests
2+
3+
4+
class ReleaseAPIClient:
5+
"""
6+
A client for interacting with the Release API.
7+
Supports authentication via username/password or personal access token.
8+
"""
9+
10+
def __init__(self, server_address, username=None, password=None, personal_access_token=None, **kwargs):
11+
"""
12+
Initializes the API client.
13+
14+
:param server_address: Base URL of the Release API server.
15+
:param username: Optional username for basic authentication.
16+
:param password: Optional password for basic authentication.
17+
:param personal_access_token: Optional personal access token for authentication.
18+
:param kwargs: Additional session parameters (e.g., headers, timeout).
19+
"""
20+
if not server_address:
21+
raise ValueError("server_address must not be empty.")
22+
23+
self.server_address = server_address.rstrip('/') # Remove trailing slash if present
24+
self.session = requests.Session()
25+
self.session.headers.update({"Accept": "application/json"})
26+
27+
# Set authentication method
28+
if username and password:
29+
self.session.auth = (username, password)
30+
elif personal_access_token:
31+
self.session.headers.update({"x-release-personal-token": personal_access_token})
32+
else:
33+
raise ValueError("Either username and password or a personal access token must be provided.")
34+
35+
# Apply additional session configurations
36+
for key, value in kwargs.items():
37+
if key == 'headers':
38+
self.session.headers.update(value) # Merge custom headers
39+
elif hasattr(self.session, key) and key != 'auth': # Skip 'auth' key
40+
setattr(self.session, key, value)
41+
42+
def _request(self, method, endpoint, params=None, json=None, data=None, **kwargs):
43+
"""
44+
Internal method to send an HTTP request.
45+
46+
:param method: HTTP method (GET, POST, PUT, DELETE, PATCH).
47+
:param endpoint: API endpoint (relative path).
48+
:param params: Optional query parameters.
49+
:param json: Optional JSON payload.
50+
:param data: Optional raw data payload.
51+
:param kwargs: Additional request options.
52+
:return: Response object.
53+
"""
54+
if not endpoint:
55+
raise ValueError("Endpoint must not be empty.")
56+
57+
kwargs.pop('auth', None) # Remove 'auth' key if present to avoid conflicts
58+
url = f"{self.server_address}/{endpoint.lstrip('/')}" # Construct full URL
59+
60+
response = self.session.request(
61+
method, url, params=params, data=data, json=json, **kwargs
62+
)
63+
64+
return response
65+
66+
def get(self, endpoint, params=None, **kwargs):
67+
"""Sends a GET request to the specified endpoint."""
68+
return self._request("GET", endpoint, params=params, **kwargs)
69+
70+
def post(self, endpoint, json=None, data=None, **kwargs):
71+
"""Sends a POST request to the specified endpoint."""
72+
return self._request("POST", endpoint, data=data, json=json, **kwargs)
73+
74+
def put(self, endpoint, json=None, data=None, **kwargs):
75+
"""Sends a PUT request to the specified endpoint."""
76+
return self._request("PUT", endpoint, data=data, json=json, **kwargs)
77+
78+
def delete(self, endpoint, params=None, **kwargs):
79+
"""Sends a DELETE request to the specified endpoint."""
80+
return self._request("DELETE", endpoint, params=params, **kwargs)
81+
82+
def patch(self, endpoint, json=None, data=None, **kwargs):
83+
"""Sends a PATCH request to the specified endpoint."""
84+
return self._request("PATCH", endpoint, data=data, json=json, **kwargs)
85+
86+
def close(self):
87+
"""Closes the session."""
88+
self.session.close()
89+
90+
def __enter__(self):
91+
"""Enables the use of 'with' statements for automatic resource management."""
92+
return self
93+
94+
def __exit__(self, exc_type, exc_value, traceback):
95+
"""Ensures the session is closed when exiting a 'with' block."""
96+
self.close()

0 commit comments

Comments
 (0)