wexample-api 0.0.74__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.
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,333 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from collections.abc import Mapping
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import requests
8
+ from wexample_helpers.classes.field import public_field
9
+ from wexample_helpers.classes.mixin.has_snake_short_class_name_class_mixin import (
10
+ HasSnakeShortClassNameClassMixin,
11
+ )
12
+ from wexample_helpers.classes.mixin.has_two_steps_init import HasTwoStepInit
13
+ from wexample_helpers.decorator.base_class import base_class
14
+ from wexample_prompt.mixins.with_io_manager import WithIoManager
15
+
16
+ from wexample_api.enums.http import ContentType, HttpMethod
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Mapping
20
+
21
+ from wexample_helpers.const.types import StringsList
22
+
23
+ from wexample_api.common.http_request_payload import HttpRequestPayload
24
+ from wexample_api.enums.http import Header
25
+
26
+
27
+ @base_class
28
+ class AbstractGateway(
29
+ HasSnakeShortClassNameClassMixin,
30
+ WithIoManager,
31
+ HasTwoStepInit,
32
+ ):
33
+ # Base configuration
34
+ base_url: str | None = public_field(default=None, description="Base API URL")
35
+ # State
36
+ connected: bool = public_field(default=False, description="Connection state")
37
+ # Default request configuration
38
+ default_headers: dict[str, str] = public_field(
39
+ factory=dict, description="Default headers for requests"
40
+ )
41
+ last_exception: Any = public_field(
42
+ default=None, description="Last exception encountered during request"
43
+ )
44
+ last_request_time: float | None = public_field(
45
+ default=None, description="Timestamp of last request"
46
+ )
47
+ quiet: bool = public_field(
48
+ default=False, description="If True, only show errors and warnings"
49
+ )
50
+ rate_limit_delay: float = public_field(
51
+ default=1.0, description="Minimum delay between requests in seconds"
52
+ )
53
+ timeout: int = public_field(default=30, description="Request timeout in seconds")
54
+
55
+ @classmethod
56
+ def get_class_name_suffix(cls) -> str | None:
57
+ return "GatewayService"
58
+
59
+ def check_connexion(self) -> bool:
60
+ return self.connected
61
+
62
+ def check_status_code(self, expected_status_codes: int | list[int] = 200) -> bool:
63
+ return (
64
+ self.make_request(
65
+ endpoint="", expected_status_codes=expected_status_codes, quiet=True
66
+ )
67
+ is not None
68
+ )
69
+
70
+ def clear_error(self) -> None:
71
+ self.last_exception = None
72
+
73
+ def connect(self) -> bool:
74
+ self.connected = True
75
+ return True
76
+
77
+ def format_response_content(self, response: requests.Response | None) -> str:
78
+ """Extract and format response content for logging."""
79
+ if response is None:
80
+ return "Null response"
81
+
82
+ try:
83
+ return response.json()
84
+ except (ValueError, AttributeError):
85
+ return response.text
86
+
87
+ def get_base_url(self) -> str | None:
88
+ return self.base_url
89
+
90
+ def get_expected_env_keys(self) -> StringsList:
91
+ return []
92
+
93
+ def get_last_error(self) -> Exception | None:
94
+ return self.last_exception
95
+
96
+ def handle_api_response(
97
+ self,
98
+ response: requests.Response | None,
99
+ request_context: HttpRequestPayload,
100
+ exception: Exception | None = None,
101
+ fatal_on_error: bool = False,
102
+ quiet: bool | None = None,
103
+ ) -> requests.Response | None:
104
+ self.last_exception = exception
105
+ is_quiet = self.quiet if quiet is None else quiet
106
+
107
+ if response is None:
108
+ if not is_quiet:
109
+ self.io.properties(
110
+ self._create_request_details(request_context),
111
+ title="Request Details",
112
+ )
113
+ if exception:
114
+ self.io.error(str(exception), exception=exception, fatal=fatal_on_error)
115
+ return None
116
+
117
+ if not is_quiet:
118
+ self.io.debug(
119
+ message=f"{request_context.method} | {response.status_code} -> {request_context.url}",
120
+ symbol="🌍",
121
+ )
122
+
123
+ # If no filtering is configured, accept all responses
124
+ if request_context.expected_status_codes is None:
125
+ return response
126
+
127
+ if response.status_code in request_context.expected_status_codes:
128
+ return response
129
+
130
+ # Combine request details with response content
131
+ details = {
132
+ **self._create_request_details(request_context, response.status_code),
133
+ "Response Content": self.format_response_content(response),
134
+ }
135
+
136
+ if not is_quiet:
137
+ self.io.properties(details, title="Request Details")
138
+
139
+ self.io.error(
140
+ message=(
141
+ str(exception) if exception else self._extract_error_message(response)
142
+ ),
143
+ exception=exception,
144
+ fatal=fatal_on_error,
145
+ )
146
+ return response
147
+
148
+ def has_error(self) -> bool:
149
+ return self.last_exception is not None
150
+
151
+ def make_request(
152
+ self,
153
+ endpoint: str,
154
+ method: HttpMethod = HttpMethod.GET,
155
+ data: dict[str, Any] | bytes | None = None,
156
+ query_params: dict[str, Any] | None = None,
157
+ headers: dict[str, str] | None = None,
158
+ files: dict[str, Any] | list[tuple] | None = None,
159
+ call_origin: str | None = None,
160
+ expected_status_codes: int | list[int] | None = None,
161
+ fatal_if_unexpected: bool = False,
162
+ quiet: bool = False,
163
+ stream: bool = False,
164
+ timeout: int | None = None,
165
+ raise_exceptions: bool = False,
166
+ ) -> requests.Response | None:
167
+ from wexample_helpers.errors.gateway_error import GatewayError
168
+
169
+ from wexample_api.common.http_request_payload import HttpRequestPayload
170
+ from wexample_api.enums.http import Header
171
+
172
+ payload = HttpRequestPayload.from_endpoint(
173
+ base_url=self.get_base_url(),
174
+ endpoint=endpoint,
175
+ method=method,
176
+ data=data,
177
+ query_params=query_params,
178
+ headers={**self.default_headers, **(headers or {})},
179
+ call_origin=call_origin,
180
+ expected_status_codes=expected_status_codes,
181
+ )
182
+
183
+ if not self.connected:
184
+ self.connect()
185
+
186
+ self._handle_rate_limiting()
187
+
188
+ # Determine how to send the data based on Content-Type header
189
+ content_type = self._get_header_value(payload.headers, Header.CONTENT_TYPE)
190
+
191
+ if files:
192
+ content_type = ContentType.MULTIPART.value
193
+ payload.headers.pop(Header.CONTENT_TYPE.value, None)
194
+
195
+ if payload.data is not None:
196
+ if isinstance(payload.data, bytes):
197
+ self.io.log(f"Sending binary payload ({len(payload.data)} bytes)")
198
+ else:
199
+ self.io.log(f"Sending {type(payload.data).__name__} payload")
200
+
201
+ request_kwargs: dict[str, Any] = {
202
+ "method": payload.method.value,
203
+ "url": payload.url,
204
+ "params": payload.query_params,
205
+ "headers": payload.headers,
206
+ "timeout": timeout or self.timeout,
207
+ "stream": stream,
208
+ }
209
+
210
+ if files:
211
+ request_kwargs["data"] = data or {}
212
+ request_kwargs["files"] = files
213
+ elif content_type in (
214
+ ContentType.FORM_URLENCODED.value,
215
+ ContentType.OCTET_STREAM.value,
216
+ ContentType.TEXT.value,
217
+ ):
218
+ request_kwargs["data"] = data
219
+ else:
220
+ request_kwargs["json"] = data
221
+
222
+ try:
223
+ response = requests.request(**request_kwargs)
224
+ except requests.exceptions.RequestException as exc:
225
+ gateway_error = GatewayError(f"Request failed: {exc}")
226
+ gateway_error.__cause__ = exc
227
+
228
+ if raise_exceptions:
229
+ raise gateway_error
230
+
231
+ return self.handle_api_response(
232
+ response=None,
233
+ request_context=payload,
234
+ exception=gateway_error,
235
+ fatal_on_error=fatal_if_unexpected,
236
+ quiet=quiet,
237
+ )
238
+
239
+ # Only check status code if expected_status_codes is explicitly provided
240
+ exception = None
241
+ if expected_status_codes is not None:
242
+ expected = (
243
+ {expected_status_codes}
244
+ if isinstance(expected_status_codes, int)
245
+ else set(expected_status_codes)
246
+ )
247
+ if response.status_code not in expected:
248
+ exception = GatewayError(self._extract_error_message(response))
249
+ exception.response = (
250
+ response # Attach response to exception for debugging
251
+ )
252
+ if raise_exceptions and exception:
253
+ raise exception
254
+
255
+ return self.handle_api_response(
256
+ response=response,
257
+ request_context=payload,
258
+ exception=exception,
259
+ fatal_on_error=fatal_if_unexpected,
260
+ quiet=quiet,
261
+ )
262
+
263
+ def setup(self) -> AbstractGateway:
264
+ if self.default_headers is None:
265
+ self.default_headers = {}
266
+
267
+ return self
268
+
269
+ def _create_request_details(
270
+ self, request_context: HttpRequestPayload, status_code: int | None = None
271
+ ) -> dict[str, Any]:
272
+ """Create request details dictionary for logging."""
273
+ from wexample_helpers.helpers.cli import cli_make_clickable_path
274
+
275
+ details: dict[str, Any] = {
276
+ "URL": request_context.url,
277
+ "Method": request_context.method,
278
+ }
279
+ if request_context.call_origin:
280
+ details["Call Origin"] = cli_make_clickable_path(
281
+ request_context.call_origin
282
+ )
283
+ if request_context.data:
284
+ if isinstance(request_context.data, bytes):
285
+ details["Data"] = f"<Binary data: {len(request_context.data)} bytes>"
286
+ else:
287
+ details["Data"] = request_context.data
288
+ if request_context.query_params:
289
+ details["Query Parameters"] = request_context.query_params
290
+ if status_code is not None:
291
+ details["Status"] = status_code
292
+ return details
293
+
294
+ def _extract_error_message(self, response: requests.Response) -> str:
295
+ """Extract error message from response."""
296
+ message = f"HTTP {response.status_code}"
297
+ try:
298
+ data = response.json()
299
+ if isinstance(data, dict):
300
+ message = data.get("message", data.get("error", message))
301
+ except (ValueError, AttributeError):
302
+ if response.text:
303
+ message = response.text
304
+ return message
305
+
306
+ def _get_header_value(
307
+ self,
308
+ headers: Mapping[str, str] | None,
309
+ name: Header,
310
+ ) -> str | None:
311
+ """
312
+ Case-insensitive lookup of a header followed by normalisation:
313
+ - keep only the part before the first ';'
314
+ - trim whitespace
315
+ - convert to lower-case
316
+ """
317
+ if not headers:
318
+ return None
319
+ raw = next(
320
+ (v for k, v in headers.items() if k.lower() == name.value.lower()),
321
+ None,
322
+ )
323
+ if raw is None:
324
+ return None
325
+
326
+ return raw.split(";", 1)[0].strip().lower() or None
327
+
328
+ def _handle_rate_limiting(self) -> None:
329
+ if self.last_request_time is not None:
330
+ elapsed = time.time() - self.last_request_time
331
+ if elapsed < self.rate_limit_delay:
332
+ time.sleep(self.rate_limit_delay - elapsed)
333
+ self.last_request_time = time.time()
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from wexample_helpers.classes.base_class import BaseClass
6
+ from wexample_helpers.classes.field import public_field
7
+ from wexample_helpers.decorator.base_class import base_class
8
+
9
+ from wexample_api.enums.http import HttpMethod
10
+
11
+
12
+ @base_class
13
+ class HttpRequestPayload(BaseClass):
14
+ call_origin: str | None = public_field(
15
+ default=None,
16
+ description="Optional identifier of the request origin",
17
+ )
18
+ data: dict[str, Any] | bytes | None = public_field(
19
+ default=None,
20
+ description="Request body as a dictionary, raw bytes, or None",
21
+ )
22
+ expected_status_codes: list[int] | None = public_field(
23
+ default=None,
24
+ description="Optional list of expected HTTP status codes. If None, all responses are accepted.",
25
+ )
26
+ headers: dict[str, str] | None = public_field(
27
+ default=None,
28
+ description="Optional HTTP headers for the request",
29
+ )
30
+ method: HttpMethod = public_field(
31
+ default=HttpMethod.GET,
32
+ description="HTTP method to use for the request",
33
+ )
34
+ query_params: dict[str, Any] | None = public_field(
35
+ default=None,
36
+ description="Optional query parameters to append to the URL",
37
+ )
38
+ url: str = public_field(
39
+ description="Target URL for the HTTP request",
40
+ )
41
+
42
+ @classmethod
43
+ def from_endpoint(
44
+ cls,
45
+ base_url: str | None,
46
+ endpoint: str,
47
+ method: HttpMethod = HttpMethod.GET,
48
+ data: dict[str, Any] | bytes | None = None,
49
+ query_params: dict[str, Any] | None = None,
50
+ headers: dict[str, str] | None = None,
51
+ call_origin: str | None = None,
52
+ expected_status_codes: int | list[int] | None = None,
53
+ ) -> HttpRequestPayload:
54
+ if base_url:
55
+ url = f"{base_url.rstrip('/')}/{endpoint.lstrip('/')}"
56
+ else:
57
+ url = endpoint
58
+
59
+ if isinstance(expected_status_codes, int):
60
+ expected_status_codes = [expected_status_codes]
61
+
62
+ return cls(
63
+ url=url,
64
+ method=method,
65
+ data=data,
66
+ query_params=query_params,
67
+ headers=headers,
68
+ call_origin=call_origin,
69
+ expected_status_codes=expected_status_codes,
70
+ )
71
+
72
+ @classmethod
73
+ def from_url(cls, url: str, call_origin: str | None = None) -> HttpRequestPayload:
74
+ return cls(url=url, call_origin=call_origin)
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from wexample_api.enums.http import HttpMethod
4
+
5
+ HTTP_METHOD_MAP = {
6
+ "GET": HttpMethod.GET,
7
+ "POST": HttpMethod.POST,
8
+ "PUT": HttpMethod.PUT,
9
+ "DELETE": HttpMethod.DELETE,
10
+ "PATCH": HttpMethod.PATCH,
11
+ "OPTIONS": HttpMethod.OPTIONS,
12
+ "HEAD": HttpMethod.HEAD,
13
+ }
File without changes
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from wexample_helpers.decorator.base_class import base_class
6
+
7
+ from wexample_api.common.abstract_gateway import AbstractGateway
8
+
9
+
10
+ @base_class
11
+ class DemoSimpleGateway(AbstractGateway):
12
+ """A simple implementation of AbstractGateway for demonstration purposes."""
13
+
14
+ def check_connection(self) -> bool:
15
+ # Always return True for demo purposes
16
+ return True
17
+
18
+ def create_item(self, item_data: dict[str, Any]) -> dict[str, Any]:
19
+ """Demo method to create an item."""
20
+ from wexample_api.enums.http import HttpMethod
21
+
22
+ response = self.make_request(
23
+ method=HttpMethod.POST,
24
+ endpoint="/items",
25
+ data=item_data,
26
+ call_origin=__file__,
27
+ )
28
+ return response.json()
29
+
30
+ def delete_item(self, item_id: str) -> None:
31
+ """Demo method to delete an item."""
32
+ from wexample_api.enums.http import HttpMethod
33
+
34
+ self.make_request(
35
+ method=HttpMethod.DELETE, endpoint=f"/items/{item_id}", call_origin=__file__
36
+ )
37
+
38
+ def get_user_info(self) -> dict[str, Any]:
39
+ """Demo method to get user information."""
40
+ from wexample_api.enums.http import HttpMethod
41
+
42
+ response = self.make_request(
43
+ method=HttpMethod.GET, endpoint="/user", call_origin=__file__
44
+ )
45
+ return response.json()
46
+
47
+ def update_item(self, item_id: str, item_data: dict[str, Any]) -> dict[str, Any]:
48
+ """Demo method to update an item."""
49
+ from wexample_api.enums.http import HttpMethod
50
+
51
+ response = self.make_request(
52
+ method=HttpMethod.PUT,
53
+ endpoint=f"/items/{item_id}",
54
+ data=item_data,
55
+ call_origin=__file__,
56
+ )
57
+ return response.json()
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class HttpMethod(Enum):
7
+ GET: str = "GET"
8
+ POST: str = "POST"
9
+ PUT: str = "PUT"
10
+ DELETE: str = "DELETE"
11
+ PATCH: str = "PATCH"
12
+ OPTIONS: str = "OPTIONS"
13
+ HEAD: str = "HEAD"
14
+
15
+
16
+ class ContentType(Enum):
17
+ JSON: str = "application/json"
18
+ FORM_URLENCODED: str = "application/x-www-form-urlencoded"
19
+ MULTIPART: str = "multipart/form-data"
20
+ TEXT: str = "text/plain"
21
+ OCTET_STREAM: str = "application/octet-stream"
22
+
23
+
24
+ class Header(Enum):
25
+ CONTENT_TYPE: str = "Content-Type"
26
+ AUTHORIZATION: str = "Authorization"
File without changes
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from wexample_helpers.errors.gateway_error import GatewayError
4
+
5
+
6
+ class GatewayAuthenticationError(GatewayError):
7
+ """Raised when authentication to the API fails."""
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from wexample_helpers.errors.gateway_error import GatewayError
4
+
5
+
6
+ class GatewayConnectionError(GatewayError):
7
+ """Raised when connection to the API fails."""
wexample_api/py.typed ADDED
File without changes
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.1
2
+ Name: wexample-api
3
+ Version: 0.0.74
4
+ Summary: Some python basic helpers for apis.
5
+ Author-Email: weeger <contact@wexample.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Project-URL: homepage, https://github.com/wexample/python-api
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: attrs>=23.1.0
13
+ Requires-Dist: cattrs>=23.1.0
14
+ Requires-Dist: requests
15
+ Requires-Dist: wexample-helpers==0.0.78
16
+ Requires-Dist: wexample-prompt==0.0.90
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest; extra == "dev"
19
+ Requires-Dist: pytest-cov; extra == "dev"
20
+ Description-Content-Type: text/markdown
21
+
22
+ # wexample-api
23
+
24
+ Version: 0.0.74
25
+
26
+ Some python basic helpers for apis.
27
+
28
+ ## Tests
29
+
30
+ This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
31
+
32
+ ### Installation
33
+
34
+ First, install the required testing dependencies:
35
+ ```bash
36
+ .venv/bin/python -m pip install pytest pytest-cov
37
+ ```
38
+
39
+ ### Basic Usage
40
+
41
+ Run all tests with coverage:
42
+ ```bash
43
+ .venv/bin/python -m pytest --cov --cov-report=html
44
+ ```
45
+
46
+ ### Common Commands
47
+ ```bash
48
+ # Run tests with coverage for a specific module
49
+ .venv/bin/python -m pytest --cov=your_module
50
+
51
+ # Show which lines are not covered
52
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
53
+
54
+ # Generate an HTML coverage report
55
+ .venv/bin/python -m pytest --cov=your_module --cov-report=html
56
+
57
+ # Combine terminal and HTML reports
58
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
59
+
60
+ # Run specific test file with coverage
61
+ .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
62
+ ```
63
+
64
+ ### Viewing HTML Reports
65
+
66
+ After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
67
+
68
+ ### Coverage Threshold
69
+
70
+ To enforce a minimum coverage percentage:
71
+ ```bash
72
+ .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
73
+ ```
74
+
75
+ This will cause the test suite to fail if coverage drops below 80%.
76
+
77
+ ## Code Quality & Typing
78
+
79
+ All the suite packages follow strict quality standards:
80
+
81
+ - **Type hints**: Full type coverage with mypy validation
82
+ - **Code formatting**: Enforced with black and isort
83
+ - **Linting**: Comprehensive checks with custom scripts and tools
84
+ - **Testing**: High test coverage requirements
85
+
86
+ These standards ensure reliability and maintainability across the suite.
87
+
88
+ ## Versioning & Compatibility Policy
89
+
90
+ Wexample packages follow **Semantic Versioning** (SemVer):
91
+
92
+ - **MAJOR**: Breaking changes
93
+ - **MINOR**: New features, backward compatible
94
+ - **PATCH**: Bug fixes, backward compatible
95
+
96
+ We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
97
+
98
+ ## Changelog
99
+
100
+ See [CHANGELOG.md](CHANGELOG.md) for detailed version history and release notes.
101
+
102
+ Major changes are documented with migration guides when applicable.
103
+
104
+ ## Migration Notes
105
+
106
+ When upgrading between major versions, refer to the migration guides in the documentation.
107
+
108
+ Breaking changes are clearly documented with upgrade paths and examples.
109
+
110
+ ## Known Limitations & Roadmap
111
+
112
+ Current limitations and planned features are tracked in the GitHub issues.
113
+
114
+ See the [project roadmap](https://github.com/wexample/python-api/issues) for upcoming features and improvements.
115
+
116
+ ## Security Policy
117
+
118
+ ### Reporting Vulnerabilities
119
+
120
+ If you discover a security vulnerability, please email security@wexample.com.
121
+
122
+ **Do not** open public issues for security vulnerabilities.
123
+
124
+ We take security seriously and will respond promptly to verified reports.
125
+
126
+ ## Privacy & Telemetry
127
+
128
+ This package does **not** collect any telemetry or usage data.
129
+
130
+ Your privacy is respected — no data is transmitted to external services.
131
+
132
+ ## Support Channels
133
+
134
+ - **GitHub Issues**: Bug reports and feature requests
135
+ - **GitHub Discussions**: Questions and community support
136
+ - **Documentation**: Comprehensive guides and API reference
137
+ - **Email**: contact@wexample.com for general inquiries
138
+
139
+ Community support is available through GitHub Discussions.
140
+
141
+ ## Contribution Guidelines
142
+
143
+ We welcome contributions to the Wexample suite!
144
+
145
+ ### How to Contribute
146
+
147
+ 1. **Fork** the repository
148
+ 2. **Create** a feature branch
149
+ 3. **Make** your changes
150
+ 4. **Test** thoroughly
151
+ 5. **Submit** a pull request
152
+
153
+ ## Maintainers & Authors
154
+
155
+ Maintained by the Wexample team and community contributors.
156
+
157
+ See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the full list of contributors.
158
+
159
+ ## License
160
+
161
+ MIT
162
+
163
+ ## Useful Links
164
+
165
+ - **Homepage**: https://github.com/wexample/python-api
166
+ - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
167
+ - **Issue Tracker**: https://github.com/wexample/python-api/issues
168
+ - **Discussions**: https://github.com/wexample/python-api/discussions
169
+ - **PyPI**: [pypi.org/project/wexample-api](https://pypi.org/project/wexample-api/)
170
+
171
+ ## Integration in the Suite
172
+
173
+ This package is part of the **Wexample Suite** — a collection of high-quality Python packages designed to work seamlessly together.
174
+
175
+ ### Related Packages
176
+
177
+ The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.
178
+
179
+ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
180
+
181
+ # About us
182
+
183
+ Wexample stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.
184
+
185
+ This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.
186
+
187
+ Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.
188
+
@@ -0,0 +1,22 @@
1
+ wexample_api-0.0.74.dist-info/METADATA,sha256=xL8OD4dLANc7ahqesbasbn-bd1xLIEf7bxYzNPsGTBI,6334
2
+ wexample_api-0.0.74.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ wexample_api-0.0.74.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ wexample_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ wexample_api/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ wexample_api/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ wexample_api/common/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ wexample_api/common/abstract_gateway.py,sha256=iNl4c0T-v0FxQpJCT4gJjTtxSlON6I5Y434Xsknt5B0,11484
9
+ wexample_api/common/http_request_payload.py,sha256=qqIlOBgvE-8tiXx8W7VWtcDCEgX_xphTrUwmxNcYum8,2436
10
+ wexample_api/const/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ wexample_api/const/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ wexample_api/const/http.py,sha256=S7S28kqVr_Cm6bXExDDAl9Q1tK2Nipb1gFPVkvtiKT8,317
13
+ wexample_api/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ wexample_api/demo/demo_simple_gateway.py,sha256=8U1TqVJso77uhL2lumh92uBboYFVAw1bwWVmbGewG34,1805
15
+ wexample_api/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ wexample_api/enums/__pycache__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ wexample_api/enums/http.py,sha256=1r2tV2EtIAxrwSBYR2X8V66jSLeTtPWoPC7Hb9w9tzc,603
18
+ wexample_api/errors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ wexample_api/errors/gateway_authentication_error.py,sha256=M2nG1CDSY1Fwa3RKbXMaaNjCFkJTkeHlsDl6JLUWg7Q,204
20
+ wexample_api/errors/gateway_connexion_error.py,sha256=ym5DtLcH_hDABPUuGzzBipfKbRp2fiI44rZ2U7usEHY,196
21
+ wexample_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ wexample_api-0.0.74.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.5)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+