nexgenpy 1.0.0__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.
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexgenpy
3
+ Version: 1.0.0
4
+ Summary: Post Shannon Communication for every day systems
5
+ Author-email: iamalreadynoob <sadikefe69@gmail.com>
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: build>=1.6.1
9
+ Requires-Dist: pre-commit>=4.6.2
10
+ Requires-Dist: requests>=2.34.2
11
+ Requires-Dist: ruff>=0.16.10
12
+
13
+ <div align="center">
14
+
15
+ # nexgenpy
16
+
17
+ **The Python client for Nexgen Server. Publish, load and run semantic communication actuators.**
18
+
19
+ [![CI](https://github.com/iamalreadynoob/nexgen/actions/workflows/ci.yml/badge.svg)](https://github.com/iamalreadynoob/nexgen/actions/workflows/ci.yml)
20
+ [![Python 3.12](https://img.shields.io/badge/python-3.12%20%7C%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org/downloads/)
21
+ [![Version](https://img.shields.io/badge/version-1.0.0-informational)](pyproject.toml)
22
+ [![Nexgen Server](https://img.shields.io/badge/server-nexgen--server-009688?logo=github&logoColor=white)](https://github.com/iamalreadynoob/nexgen-server)
23
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
24
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
25
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-FAB040?logo=pre-commit&logoColor=white)](.pre-commit-config.yaml)
26
+
27
+ <p>
28
+ <a href="#introduction">Introduction</a> •
29
+ <a href="#setup">Setup</a> •
30
+ <a href="#basic-concepts">Basic concepts</a> •
31
+ <a href="#documentation">Documentation</a>
32
+ </p>
33
+
34
+ </div>
35
+
36
+ ---
37
+
38
+ ## Introduction
39
+
40
+ `nexgenpy` is the Python client library for [Nexgen Server](https://github.com/iamalreadynoob/nexgen-server). Nexgen Server is a registry and data store for semantic communication (SemCom) applications.
41
+
42
+ SemCom is a communication paradigm. Its design objective is the correct delivery of the *meaning*, or of the *task-relevant content*, of a message. Its objective is not the bit-exact copy of the transmitted symbols. Thus the methods at each end of the link are as important as the link itself.
43
+
44
+ Nexgen Server keeps these methods, called **actuators**, in one place. The server does not run actuator code. `nexgenpy` gives the applications three operations:
45
+
46
+ | Operation | API | What it does |
47
+ |---|---|---|
48
+ | **Register** | `@actuator(...)` | Sends the source code of a Python function to the server as a new actuator version. |
49
+ | **Load** | `load_actuator(...)` | Downloads the latest version of an actuator to a local directory and installs its requirements. |
50
+ | **Use** | `run_actuator(...)`, `Actuator(...)` | Runs the local actuator. Optionally sends the output to the server as a data item. |
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ subgraph DEV["Developer"]
55
+ F["Python function<br/>+ @actuator decorator"]
56
+ end
57
+
58
+ NS[("Nexgen Server<br/>actuators · data · contents")]
59
+
60
+ subgraph TX["Transmitter (nexgenpy)"]
61
+ S["Source s"] --> CA["coding actuator"]
62
+ end
63
+
64
+ subgraph RX["Receiver (nexgenpy)"]
65
+ DA["decoding actuator"] --> O["Output ŝ<br/>reconstruction or task decision"]
66
+ end
67
+
68
+ F -- "1. register<br/>POST /actuators" --> NS
69
+ NS -. "2. load<br/>GET /actuators/{name}/latest" .-> CA
70
+ NS -. "2. load" .-> DA
71
+ CA -- "3. use: transmitted representation" --> DA
72
+ CA -. "data item<br/>POST /data" .-> NS
73
+ S -. "content item (original)<br/>POST /contents" .-> NS
74
+ ```
75
+
76
+ 1. A developer writes a Python function and registers it as an actuator with a name and a version.
77
+ 2. A transmitter and a receiver each load the latest version of the actuators that they need.
78
+ 3. Each side runs its actuator on local data. The output can go to the server as a data item. The original input can go to the server as a content item.
79
+
80
+ `nexgenpy` supports the two SemCom objectives of Nexgen Server:
81
+
82
+ - **Semantic-level communication.** The decoding actuator recreates the source, or its meaning, from the received representation.
83
+ - **Goal-oriented (task-oriented) communication.** The decoding actuator identifies the input or does a task with it, for example classification. It does not recreate the source.
84
+
85
+ > [!TIP]
86
+ > Do you use [Claude Code](https://claude.com/claude-code)? The [nexgenpy skill](https://github.com/iamalreadynoob/nexgenpy-skill) gives Claude Code the knowledge to use `nexgenpy` in your projects. Install it to get help when you register, load and use actuators.
87
+
88
+ ## Setup
89
+
90
+ ### Requirements
91
+
92
+ - Python 3.12 or later
93
+ - A running [Nexgen Server](https://github.com/iamalreadynoob/nexgen-server#setup)
94
+ - The API key of the server, if the server has one
95
+
96
+ ### Install the library
97
+
98
+ Install from GitHub with pip:
99
+
100
+ ```bash
101
+ pip install "git+https://github.com/iamalreadynoob/nexgen.git"
102
+ ```
103
+
104
+ Or add it to a uv project:
105
+
106
+ ```bash
107
+ uv add "git+https://github.com/iamalreadynoob/nexgen.git"
108
+ ```
109
+
110
+ The repository also keeps a built wheel in `dist/`. To install a local clone for development, see [Development](docs/development.md).
111
+
112
+ ### Connect to the server
113
+
114
+ Set these environment variables:
115
+
116
+ ```bash
117
+ export NEXGEN_BASE_URL="http://localhost:8000/api/v1"
118
+ export NEXGEN_API_KEY="<your-key>"
119
+ ```
120
+
121
+ If `NEXGEN_BASE_URL` is not set, the library uses `http://localhost:8000/api/v1`. See [Configuration](docs/configuration.md) for all settings.
122
+
123
+ ### Register a first actuator
124
+
125
+ ```python
126
+ from nexgenpy import actuator
127
+
128
+
129
+ @actuator(
130
+ name="text-embedder",
131
+ atype="coding",
132
+ version="1.0.0",
133
+ is_latest=True,
134
+ requirements=["numpy"],
135
+ )
136
+ def encode(text: str) -> list[float]:
137
+ import numpy as np
138
+
139
+ return np.frombuffer(text.encode(), dtype=np.uint8).astype(float).tolist()
140
+
141
+
142
+ accepted = encode() # sends POST /actuators, returns True or False
143
+ ```
144
+
145
+ A call to the decorated function does not run `encode`. The call registers it.
146
+
147
+ ### Load and use the actuator
148
+
149
+ ```python
150
+ from nexgenpy import load_actuator, run_actuator
151
+
152
+ load_actuator("text-embedder")
153
+
154
+ vector = run_actuator(
155
+ name="text-embedder",
156
+ data={"text": "hello"},
157
+ )
158
+ ```
159
+
160
+ `run_actuator` runs the function in the current Python process. Thus the current environment must also have the requirements of the actuator, `numpy` in this example. See [Architecture](docs/architecture.md#the-actuator-environment).
161
+
162
+ To send the output to the server as a data item, give the server URL and the API key header:
163
+
164
+ ```python
165
+ vector = run_actuator(
166
+ name="text-embedder",
167
+ data={"text": "hello"},
168
+ dst="http://localhost:8000/api/v1",
169
+ dst_headers={"X-API-Key": "<your-key>"},
170
+ )
171
+ ```
172
+
173
+ The [Quick start guide](docs/quickstart.md) gives a full transmitter and receiver example.
174
+
175
+ ## Basic concepts
176
+
177
+ | Concept | What it is | Where it lives |
178
+ |---|---|---|
179
+ | **Actuator** | A versioned, named Python function that processes data. Its type is `coding` or `decoding`. | Server: `actuators` collection. Client: `nexgen_actuators/{name}/` |
180
+ | **Data** | The output of an actuator, as the link carries or uses it. Each data item refers to the actuator that made it. | Server: `data` collection |
181
+ | **Content** | The original input of an actuator. Applications can load it again, for example to compare it with a reconstruction. | Server: `contents` collection |
182
+
183
+ ### Actuators
184
+
185
+ An actuator does one processing step in a SemCom pipeline. Examples are data embedding, compression and format conversion.
186
+
187
+ - A **`coding`** actuator changes a source into a representation for transmission. It can be a semantic encoder, a channel encoder or a joint source–channel coding (JSCC) encoder.
188
+ - A **`decoding`** actuator changes a received representation into an output. The output is a reconstruction of the source or a task decision.
189
+
190
+ Each actuator has a `name` and a `version`. For each name, exactly one active version is the latest. `load_actuator` always gets the latest version. Thus the transmitter and the receiver can use the same version.
191
+
192
+ ### The actuator function
193
+
194
+ The server stores only the source code of the function. The code that is outside the function does not go to the server. Thus an actuator function must obey these rules:
195
+
196
+ - Put all `import` statements in the function body.
197
+ - Do not call helper functions from the same module. Define them in the function body.
198
+ - Use parameter names that match the keys of the input data. `run_actuator` calls the function with `**data`.
199
+ - Return a value that JSON can encode, if you send the output to the server.
200
+
201
+ [Writing actuators](docs/writing-actuators.md) explains each rule.
202
+
203
+ ### Local actuator directory
204
+
205
+ `load_actuator` writes each actuator to `nexgen_actuators/{name}/`:
206
+
207
+ ```text
208
+ nexgen_actuators/
209
+ └── text-embedder/
210
+ ├── card.json # metadata, including the server id and the version
211
+ ├── method.py # the source code of the function
212
+ ├── requirements.txt # the pip requirements
213
+ └── .venv/ # a virtual environment with the requirements
214
+ ```
215
+
216
+ ### Shared rules
217
+
218
+ - All requests go to `NEXGEN_BASE_URL`, or to `dst` for data and content items.
219
+ - The library sends the `X-API-KEY` header when `NEXGEN_API_KEY` is set. Data and content requests use `dst_headers` instead.
220
+ - The server accepts a data item only from the **latest** version of an actuator. Load the actuator again after someone publishes a new version.
221
+
222
+ [Concepts](docs/concepts.md) gives more detail about SemCom and about how `nexgenpy` fits a SemCom pipeline.
223
+
224
+ ## Documentation
225
+
226
+ | Document | Contents |
227
+ |---|---|
228
+ | [Quick start](docs/quickstart.md) | A full example: register, load and run a coding and a decoding actuator |
229
+ | [Concepts](docs/concepts.md) | SemCom background, the two objectives, and how actuators, data and contents fit a SemCom pipeline |
230
+ | [Architecture](docs/architecture.md) | Modules, the request flow for each operation and the local directory layout |
231
+ | [Writing actuators](docs/writing-actuators.md) | Rules and examples for actuator functions |
232
+ | [Configuration](docs/configuration.md) | Environment variables, API key, timeouts and logging |
233
+ | [API: `actuator`](docs/api/actuator.md) | The decorator that registers an actuator |
234
+ | [API: `load_actuator`](docs/api/load-actuator.md) | The function that downloads an actuator and installs its requirements |
235
+ | [API: `run_actuator` and `Actuator`](docs/api/run-actuator.md) | The function and the class that run an actuator and send its output |
236
+ | [Development](docs/development.md) | Tests, lint, CI, release and code conventions |
237
+ | [Security](docs/security.md) | Risks of downloaded actuator code, and SemCom threats |
238
+ | [Glossary](docs/glossary.md) | SemCom and project terms |
239
+ | [References](docs/references.md) | Surveys and papers about SemCom |
@@ -0,0 +1,227 @@
1
+ <div align="center">
2
+
3
+ # nexgenpy
4
+
5
+ **The Python client for Nexgen Server. Publish, load and run semantic communication actuators.**
6
+
7
+ [![CI](https://github.com/iamalreadynoob/nexgen/actions/workflows/ci.yml/badge.svg)](https://github.com/iamalreadynoob/nexgen/actions/workflows/ci.yml)
8
+ [![Python 3.12](https://img.shields.io/badge/python-3.12%20%7C%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org/downloads/)
9
+ [![Version](https://img.shields.io/badge/version-1.0.0-informational)](pyproject.toml)
10
+ [![Nexgen Server](https://img.shields.io/badge/server-nexgen--server-009688?logo=github&logoColor=white)](https://github.com/iamalreadynoob/nexgen-server)
11
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
12
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
13
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-FAB040?logo=pre-commit&logoColor=white)](.pre-commit-config.yaml)
14
+
15
+ <p>
16
+ <a href="#introduction">Introduction</a> •
17
+ <a href="#setup">Setup</a> •
18
+ <a href="#basic-concepts">Basic concepts</a> •
19
+ <a href="#documentation">Documentation</a>
20
+ </p>
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## Introduction
27
+
28
+ `nexgenpy` is the Python client library for [Nexgen Server](https://github.com/iamalreadynoob/nexgen-server). Nexgen Server is a registry and data store for semantic communication (SemCom) applications.
29
+
30
+ SemCom is a communication paradigm. Its design objective is the correct delivery of the *meaning*, or of the *task-relevant content*, of a message. Its objective is not the bit-exact copy of the transmitted symbols. Thus the methods at each end of the link are as important as the link itself.
31
+
32
+ Nexgen Server keeps these methods, called **actuators**, in one place. The server does not run actuator code. `nexgenpy` gives the applications three operations:
33
+
34
+ | Operation | API | What it does |
35
+ |---|---|---|
36
+ | **Register** | `@actuator(...)` | Sends the source code of a Python function to the server as a new actuator version. |
37
+ | **Load** | `load_actuator(...)` | Downloads the latest version of an actuator to a local directory and installs its requirements. |
38
+ | **Use** | `run_actuator(...)`, `Actuator(...)` | Runs the local actuator. Optionally sends the output to the server as a data item. |
39
+
40
+ ```mermaid
41
+ flowchart LR
42
+ subgraph DEV["Developer"]
43
+ F["Python function<br/>+ @actuator decorator"]
44
+ end
45
+
46
+ NS[("Nexgen Server<br/>actuators · data · contents")]
47
+
48
+ subgraph TX["Transmitter (nexgenpy)"]
49
+ S["Source s"] --> CA["coding actuator"]
50
+ end
51
+
52
+ subgraph RX["Receiver (nexgenpy)"]
53
+ DA["decoding actuator"] --> O["Output ŝ<br/>reconstruction or task decision"]
54
+ end
55
+
56
+ F -- "1. register<br/>POST /actuators" --> NS
57
+ NS -. "2. load<br/>GET /actuators/{name}/latest" .-> CA
58
+ NS -. "2. load" .-> DA
59
+ CA -- "3. use: transmitted representation" --> DA
60
+ CA -. "data item<br/>POST /data" .-> NS
61
+ S -. "content item (original)<br/>POST /contents" .-> NS
62
+ ```
63
+
64
+ 1. A developer writes a Python function and registers it as an actuator with a name and a version.
65
+ 2. A transmitter and a receiver each load the latest version of the actuators that they need.
66
+ 3. Each side runs its actuator on local data. The output can go to the server as a data item. The original input can go to the server as a content item.
67
+
68
+ `nexgenpy` supports the two SemCom objectives of Nexgen Server:
69
+
70
+ - **Semantic-level communication.** The decoding actuator recreates the source, or its meaning, from the received representation.
71
+ - **Goal-oriented (task-oriented) communication.** The decoding actuator identifies the input or does a task with it, for example classification. It does not recreate the source.
72
+
73
+ > [!TIP]
74
+ > Do you use [Claude Code](https://claude.com/claude-code)? The [nexgenpy skill](https://github.com/iamalreadynoob/nexgenpy-skill) gives Claude Code the knowledge to use `nexgenpy` in your projects. Install it to get help when you register, load and use actuators.
75
+
76
+ ## Setup
77
+
78
+ ### Requirements
79
+
80
+ - Python 3.12 or later
81
+ - A running [Nexgen Server](https://github.com/iamalreadynoob/nexgen-server#setup)
82
+ - The API key of the server, if the server has one
83
+
84
+ ### Install the library
85
+
86
+ Install from GitHub with pip:
87
+
88
+ ```bash
89
+ pip install "git+https://github.com/iamalreadynoob/nexgen.git"
90
+ ```
91
+
92
+ Or add it to a uv project:
93
+
94
+ ```bash
95
+ uv add "git+https://github.com/iamalreadynoob/nexgen.git"
96
+ ```
97
+
98
+ The repository also keeps a built wheel in `dist/`. To install a local clone for development, see [Development](docs/development.md).
99
+
100
+ ### Connect to the server
101
+
102
+ Set these environment variables:
103
+
104
+ ```bash
105
+ export NEXGEN_BASE_URL="http://localhost:8000/api/v1"
106
+ export NEXGEN_API_KEY="<your-key>"
107
+ ```
108
+
109
+ If `NEXGEN_BASE_URL` is not set, the library uses `http://localhost:8000/api/v1`. See [Configuration](docs/configuration.md) for all settings.
110
+
111
+ ### Register a first actuator
112
+
113
+ ```python
114
+ from nexgenpy import actuator
115
+
116
+
117
+ @actuator(
118
+ name="text-embedder",
119
+ atype="coding",
120
+ version="1.0.0",
121
+ is_latest=True,
122
+ requirements=["numpy"],
123
+ )
124
+ def encode(text: str) -> list[float]:
125
+ import numpy as np
126
+
127
+ return np.frombuffer(text.encode(), dtype=np.uint8).astype(float).tolist()
128
+
129
+
130
+ accepted = encode() # sends POST /actuators, returns True or False
131
+ ```
132
+
133
+ A call to the decorated function does not run `encode`. The call registers it.
134
+
135
+ ### Load and use the actuator
136
+
137
+ ```python
138
+ from nexgenpy import load_actuator, run_actuator
139
+
140
+ load_actuator("text-embedder")
141
+
142
+ vector = run_actuator(
143
+ name="text-embedder",
144
+ data={"text": "hello"},
145
+ )
146
+ ```
147
+
148
+ `run_actuator` runs the function in the current Python process. Thus the current environment must also have the requirements of the actuator, `numpy` in this example. See [Architecture](docs/architecture.md#the-actuator-environment).
149
+
150
+ To send the output to the server as a data item, give the server URL and the API key header:
151
+
152
+ ```python
153
+ vector = run_actuator(
154
+ name="text-embedder",
155
+ data={"text": "hello"},
156
+ dst="http://localhost:8000/api/v1",
157
+ dst_headers={"X-API-Key": "<your-key>"},
158
+ )
159
+ ```
160
+
161
+ The [Quick start guide](docs/quickstart.md) gives a full transmitter and receiver example.
162
+
163
+ ## Basic concepts
164
+
165
+ | Concept | What it is | Where it lives |
166
+ |---|---|---|
167
+ | **Actuator** | A versioned, named Python function that processes data. Its type is `coding` or `decoding`. | Server: `actuators` collection. Client: `nexgen_actuators/{name}/` |
168
+ | **Data** | The output of an actuator, as the link carries or uses it. Each data item refers to the actuator that made it. | Server: `data` collection |
169
+ | **Content** | The original input of an actuator. Applications can load it again, for example to compare it with a reconstruction. | Server: `contents` collection |
170
+
171
+ ### Actuators
172
+
173
+ An actuator does one processing step in a SemCom pipeline. Examples are data embedding, compression and format conversion.
174
+
175
+ - A **`coding`** actuator changes a source into a representation for transmission. It can be a semantic encoder, a channel encoder or a joint source–channel coding (JSCC) encoder.
176
+ - A **`decoding`** actuator changes a received representation into an output. The output is a reconstruction of the source or a task decision.
177
+
178
+ Each actuator has a `name` and a `version`. For each name, exactly one active version is the latest. `load_actuator` always gets the latest version. Thus the transmitter and the receiver can use the same version.
179
+
180
+ ### The actuator function
181
+
182
+ The server stores only the source code of the function. The code that is outside the function does not go to the server. Thus an actuator function must obey these rules:
183
+
184
+ - Put all `import` statements in the function body.
185
+ - Do not call helper functions from the same module. Define them in the function body.
186
+ - Use parameter names that match the keys of the input data. `run_actuator` calls the function with `**data`.
187
+ - Return a value that JSON can encode, if you send the output to the server.
188
+
189
+ [Writing actuators](docs/writing-actuators.md) explains each rule.
190
+
191
+ ### Local actuator directory
192
+
193
+ `load_actuator` writes each actuator to `nexgen_actuators/{name}/`:
194
+
195
+ ```text
196
+ nexgen_actuators/
197
+ └── text-embedder/
198
+ ├── card.json # metadata, including the server id and the version
199
+ ├── method.py # the source code of the function
200
+ ├── requirements.txt # the pip requirements
201
+ └── .venv/ # a virtual environment with the requirements
202
+ ```
203
+
204
+ ### Shared rules
205
+
206
+ - All requests go to `NEXGEN_BASE_URL`, or to `dst` for data and content items.
207
+ - The library sends the `X-API-KEY` header when `NEXGEN_API_KEY` is set. Data and content requests use `dst_headers` instead.
208
+ - The server accepts a data item only from the **latest** version of an actuator. Load the actuator again after someone publishes a new version.
209
+
210
+ [Concepts](docs/concepts.md) gives more detail about SemCom and about how `nexgenpy` fits a SemCom pipeline.
211
+
212
+ ## Documentation
213
+
214
+ | Document | Contents |
215
+ |---|---|
216
+ | [Quick start](docs/quickstart.md) | A full example: register, load and run a coding and a decoding actuator |
217
+ | [Concepts](docs/concepts.md) | SemCom background, the two objectives, and how actuators, data and contents fit a SemCom pipeline |
218
+ | [Architecture](docs/architecture.md) | Modules, the request flow for each operation and the local directory layout |
219
+ | [Writing actuators](docs/writing-actuators.md) | Rules and examples for actuator functions |
220
+ | [Configuration](docs/configuration.md) | Environment variables, API key, timeouts and logging |
221
+ | [API: `actuator`](docs/api/actuator.md) | The decorator that registers an actuator |
222
+ | [API: `load_actuator`](docs/api/load-actuator.md) | The function that downloads an actuator and installs its requirements |
223
+ | [API: `run_actuator` and `Actuator`](docs/api/run-actuator.md) | The function and the class that run an actuator and send its output |
224
+ | [Development](docs/development.md) | Tests, lint, CI, release and code conventions |
225
+ | [Security](docs/security.md) | Risks of downloaded actuator code, and SemCom threats |
226
+ | [Glossary](docs/glossary.md) | SemCom and project terms |
227
+ | [References](docs/references.md) | Surveys and papers about SemCom |
@@ -0,0 +1,2 @@
1
+ from .actuators import actuator
2
+ from .manager import Actuator, load_actuator, run_actuator
@@ -0,0 +1,185 @@
1
+ """# Actuators.
2
+
3
+ It contains the decorators for the actuators.
4
+ """
5
+
6
+ import ast
7
+ import inspect
8
+ import logging
9
+ import os
10
+ import textwrap
11
+ from functools import wraps
12
+ from typing import Any, Callable, Dict, List, Literal, get_type_hints
13
+
14
+ import requests
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ DEFAULT_BASE_URL = "http://localhost:8000/api/v1"
19
+ DEFAULT_TIMEOUT = 60
20
+
21
+
22
+ def _function_source(func: Callable[..., Any]) -> str:
23
+ """# Function Source.
24
+
25
+ It returns the source code of the function without its decorators, so the
26
+ downloaded method.py does not need to import them.
27
+
28
+ Args:
29
+ func (Callable[..., Any]): The decorated function.
30
+
31
+ Returns:
32
+ str: The source code that starts at the `def` line.
33
+ """
34
+ source = textwrap.dedent(inspect.getsource(func))
35
+ node = ast.parse(source).body[0]
36
+ lines = source.splitlines(keepends=True)
37
+ return "".join(lines[node.lineno - 1 :])
38
+
39
+
40
+ def _request_timeout() -> float:
41
+ """# Request Timeout.
42
+
43
+ Returns:
44
+ float: The value of NEXGEN_TIMEOUT, or the default if it is not valid.
45
+ """
46
+ raw_timeout = os.getenv("NEXGEN_TIMEOUT")
47
+ if raw_timeout is None:
48
+ return DEFAULT_TIMEOUT
49
+
50
+ try:
51
+ timeout = float(raw_timeout)
52
+ except ValueError:
53
+ timeout = 0
54
+
55
+ if timeout <= 0:
56
+ logger.warning(
57
+ "NEXGEN_TIMEOUT is invalid: %r, using %d",
58
+ raw_timeout,
59
+ DEFAULT_TIMEOUT,
60
+ )
61
+ return DEFAULT_TIMEOUT
62
+
63
+ return timeout
64
+
65
+
66
+ def actuator(
67
+ name: str,
68
+ atype: Literal["coding", "decoding"],
69
+ version: str,
70
+ is_latest: bool,
71
+ requirements: List[str] | None,
72
+ ) -> Callable[[Callable[..., Any]], Callable[..., bool]]:
73
+ """# Actuator.
74
+
75
+ It holds the information for the actuator. A call to the decorated function
76
+ publishes the actuator to the Nexgen API.
77
+
78
+ Args:
79
+ name (str): Name of the actuator.
80
+ atype (Literal["coding", "decoding"]): Type of the actuator.
81
+ version (str): Version of the actuator.
82
+ is_latest (bool): If it is true, the server marks this version as latest.
83
+ requirements (List[str] | None): Pip requirements of the actuator.
84
+
85
+ Returns:
86
+ Callable[[Callable[..., Any]], Callable[..., bool]]: The decorator.
87
+ """
88
+ # Checking The Datatypes
89
+ if isinstance(name, str) is False:
90
+ logger.error("name is invalid, it has to be str")
91
+ raise ValueError("name is invalid, it has to be str")
92
+
93
+ if atype not in ["coding", "decoding"]:
94
+ logger.error("atype is invalid, it has to be either coding or decoding")
95
+ raise ValueError("atype is invalid, it has to be either coding or decoding")
96
+
97
+ if isinstance(version, str) is False:
98
+ logger.error("version is invalid, it has to be str")
99
+ raise ValueError("version is invalid, it has to be str")
100
+
101
+ if isinstance(is_latest, bool) is False:
102
+ logger.error("is_latest is invalid, it has to be bool")
103
+ raise ValueError("is_latest is invalid, it has to be bool")
104
+
105
+ if requirements is not None and (
106
+ isinstance(requirements, list) is False
107
+ or all(isinstance(r, str) for r in requirements) is False
108
+ ):
109
+ logger.error("requirements is invalid, it has to be List[str]")
110
+ raise ValueError("requirements is invalid, it has to be List[str]")
111
+
112
+ def decorator(func: Callable[..., Any]) -> Callable[..., bool]:
113
+ """# Decorator Method."""
114
+ signature = inspect.signature(func)
115
+ type_hints = get_type_hints(func)
116
+
117
+ arguments: Dict[str, Dict[str, str]] = {}
118
+
119
+ # The server accepts only str values, so missing info is left out
120
+ for parameter_name, parameter in signature.parameters.items():
121
+ annotation = type_hints.get(parameter_name)
122
+ argument: Dict[str, str] = {}
123
+
124
+ if annotation is not None:
125
+ argument["type"] = str(annotation)
126
+
127
+ if parameter.default is not inspect.Parameter.empty:
128
+ argument["default"] = str(parameter.default)
129
+
130
+ arguments[parameter_name] = argument
131
+
132
+ sample: Dict[str, Any] = {
133
+ "name": name,
134
+ "version": version,
135
+ "actuator_type": atype,
136
+ "set_latest": is_latest,
137
+ "requirements": requirements if requirements is not None else [],
138
+ "method": _function_source(func),
139
+ "method_args": arguments,
140
+ }
141
+
142
+ @wraps(func)
143
+ def wrapper(*args: Any, **kwargs: Any) -> bool: # noqa: ANN401, ARG001
144
+ """# Wrapper Method.
145
+
146
+ Returns:
147
+ bool: True if the server accepted the actuator, else False.
148
+ """
149
+ base_url = os.getenv("NEXGEN_BASE_URL", DEFAULT_BASE_URL)
150
+
151
+ api_key = os.getenv("NEXGEN_API_KEY")
152
+ headers: Dict[str, str] = {}
153
+
154
+ if api_key is not None:
155
+ headers["X-API-KEY"] = api_key
156
+
157
+ try:
158
+ result = requests.post(
159
+ f"{base_url}/actuators",
160
+ headers=headers,
161
+ json=sample,
162
+ timeout=_request_timeout(),
163
+ )
164
+ except requests.RequestException as e:
165
+ logger.warning("POST /actuators failed: %s", e)
166
+ return False
167
+
168
+ if result.ok is True:
169
+ logger.info(
170
+ "POST /actuators succeeded: status=%d, response=%s",
171
+ result.status_code,
172
+ result.text,
173
+ )
174
+ return True
175
+
176
+ logger.warning(
177
+ "POST /actuators failed: status=%d, response=%s",
178
+ result.status_code,
179
+ result.text,
180
+ )
181
+ return False
182
+
183
+ return wrapper
184
+
185
+ return decorator