openapi-httpx-client 0.1.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.
- openapi_httpx_client-0.1.0/PKG-INFO +75 -0
- openapi_httpx_client-0.1.0/README.md +51 -0
- openapi_httpx_client-0.1.0/openapi_httpx_client.egg-info/PKG-INFO +75 -0
- openapi_httpx_client-0.1.0/openapi_httpx_client.egg-info/SOURCES.txt +9 -0
- openapi_httpx_client-0.1.0/openapi_httpx_client.egg-info/dependency_links.txt +1 -0
- openapi_httpx_client-0.1.0/openapi_httpx_client.egg-info/requires.txt +2 -0
- openapi_httpx_client-0.1.0/openapi_httpx_client.egg-info/top_level.txt +1 -0
- openapi_httpx_client-0.1.0/openapiclient/__init__.py +3 -0
- openapi_httpx_client-0.1.0/openapiclient/client.py +219 -0
- openapi_httpx_client-0.1.0/setup.cfg +4 -0
- openapi_httpx_client-0.1.0/setup.py +27 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: openapi-httpx-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python client for OpenAPI specifications using httpx
|
|
5
|
+
Home-page: https://github.com/lloydzhou/openapiclient
|
|
6
|
+
Author: lloydzhou
|
|
7
|
+
Author-email: lloydzhou@qq.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.23.0
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# OpenAPI Client for Python
|
|
26
|
+
|
|
27
|
+
A Python implementation inspired by [openapi-client-axios](https://github.com/openapistack/openapi-client-axios) that provides a dynamic client for OpenAPI specifications. This implementation uses httpx for HTTP requests and Python's metaprogramming capabilities to dynamically generate a client.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from openapiclient import OpenAPIClient
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
# Initialize the client with the OpenAPI definition
|
|
43
|
+
api = OpenAPIClient(definition="https://petstore3.swagger.io/api/v3/openapi.json")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Initialize and get the dynamic client
|
|
47
|
+
client = await api.init()
|
|
48
|
+
|
|
49
|
+
# Call an operation using the generated method
|
|
50
|
+
response = await client.getPetById(petId=1)
|
|
51
|
+
|
|
52
|
+
# Print the response
|
|
53
|
+
print(f"Status code: {response['status']}")
|
|
54
|
+
print(f"Pet data: {response['data']}")
|
|
55
|
+
finally:
|
|
56
|
+
# Close the HTTP session
|
|
57
|
+
await api.close()
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- Dynamic client generation using Python metaprogramming
|
|
66
|
+
- Supports OpenAPI 3.0 and 3.1
|
|
67
|
+
- Supports JSON and YAML specification formats
|
|
68
|
+
- Supports specification loading from URL, file, or dictionary
|
|
69
|
+
- Asynchronous API using httpx
|
|
70
|
+
- Response format similar to axios (data, status, headers, config)
|
|
71
|
+
|
|
72
|
+
## Author
|
|
73
|
+
lloydzhou (2025-02-27)
|
|
74
|
+
|
|
75
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# OpenAPI Client for Python
|
|
2
|
+
|
|
3
|
+
A Python implementation inspired by [openapi-client-axios](https://github.com/openapistack/openapi-client-axios) that provides a dynamic client for OpenAPI specifications. This implementation uses httpx for HTTP requests and Python's metaprogramming capabilities to dynamically generate a client.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from openapiclient import OpenAPIClient
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
async def main():
|
|
18
|
+
# Initialize the client with the OpenAPI definition
|
|
19
|
+
api = OpenAPIClient(definition="https://petstore3.swagger.io/api/v3/openapi.json")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
# Initialize and get the dynamic client
|
|
23
|
+
client = await api.init()
|
|
24
|
+
|
|
25
|
+
# Call an operation using the generated method
|
|
26
|
+
response = await client.getPetById(petId=1)
|
|
27
|
+
|
|
28
|
+
# Print the response
|
|
29
|
+
print(f"Status code: {response['status']}")
|
|
30
|
+
print(f"Pet data: {response['data']}")
|
|
31
|
+
finally:
|
|
32
|
+
# Close the HTTP session
|
|
33
|
+
await api.close()
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
asyncio.run(main())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- Dynamic client generation using Python metaprogramming
|
|
42
|
+
- Supports OpenAPI 3.0 and 3.1
|
|
43
|
+
- Supports JSON and YAML specification formats
|
|
44
|
+
- Supports specification loading from URL, file, or dictionary
|
|
45
|
+
- Asynchronous API using httpx
|
|
46
|
+
- Response format similar to axios (data, status, headers, config)
|
|
47
|
+
|
|
48
|
+
## Author
|
|
49
|
+
lloydzhou (2025-02-27)
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: openapi-httpx-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python client for OpenAPI specifications using httpx
|
|
5
|
+
Home-page: https://github.com/lloydzhou/openapiclient
|
|
6
|
+
Author: lloydzhou
|
|
7
|
+
Author-email: lloydzhou@qq.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.23.0
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# OpenAPI Client for Python
|
|
26
|
+
|
|
27
|
+
A Python implementation inspired by [openapi-client-axios](https://github.com/openapistack/openapi-client-axios) that provides a dynamic client for OpenAPI specifications. This implementation uses httpx for HTTP requests and Python's metaprogramming capabilities to dynamically generate a client.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from openapiclient import OpenAPIClient
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
# Initialize the client with the OpenAPI definition
|
|
43
|
+
api = OpenAPIClient(definition="https://petstore3.swagger.io/api/v3/openapi.json")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Initialize and get the dynamic client
|
|
47
|
+
client = await api.init()
|
|
48
|
+
|
|
49
|
+
# Call an operation using the generated method
|
|
50
|
+
response = await client.getPetById(petId=1)
|
|
51
|
+
|
|
52
|
+
# Print the response
|
|
53
|
+
print(f"Status code: {response['status']}")
|
|
54
|
+
print(f"Pet data: {response['data']}")
|
|
55
|
+
finally:
|
|
56
|
+
# Close the HTTP session
|
|
57
|
+
await api.close()
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- Dynamic client generation using Python metaprogramming
|
|
66
|
+
- Supports OpenAPI 3.0 and 3.1
|
|
67
|
+
- Supports JSON and YAML specification formats
|
|
68
|
+
- Supports specification loading from URL, file, or dictionary
|
|
69
|
+
- Asynchronous API using httpx
|
|
70
|
+
- Response format similar to axios (data, status, headers, config)
|
|
71
|
+
|
|
72
|
+
## Author
|
|
73
|
+
lloydzhou (2025-02-27)
|
|
74
|
+
|
|
75
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
openapi_httpx_client.egg-info/PKG-INFO
|
|
4
|
+
openapi_httpx_client.egg-info/SOURCES.txt
|
|
5
|
+
openapi_httpx_client.egg-info/dependency_links.txt
|
|
6
|
+
openapi_httpx_client.egg-info/requires.txt
|
|
7
|
+
openapi_httpx_client.egg-info/top_level.txt
|
|
8
|
+
openapiclient/__init__.py
|
|
9
|
+
openapiclient/client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openapiclient
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import json
|
|
3
|
+
import os.path
|
|
4
|
+
from urllib.parse import urljoin, urlparse
|
|
5
|
+
import yaml
|
|
6
|
+
import types
|
|
7
|
+
|
|
8
|
+
class OpenAPIClient:
|
|
9
|
+
"""
|
|
10
|
+
A Python client for OpenAPI specifications, inspired by openapi-client-axios.
|
|
11
|
+
Uses httpx for HTTP requests.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, definition=None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the OpenAPI client.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
definition: URL or file path to the OpenAPI definition, or a dictionary containing the definition
|
|
20
|
+
"""
|
|
21
|
+
self.definition_source = definition
|
|
22
|
+
self.definition = None
|
|
23
|
+
self.client = None
|
|
24
|
+
self.base_url = ''
|
|
25
|
+
self.session = None
|
|
26
|
+
self.source_url = None # Store the source URL if loaded from a URL
|
|
27
|
+
|
|
28
|
+
async def init(self):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the client by loading and parsing the OpenAPI definition.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
DynamicClient: A client with methods generated from the OpenAPI definition
|
|
34
|
+
"""
|
|
35
|
+
# Load the OpenAPI definition
|
|
36
|
+
await self.load_definition()
|
|
37
|
+
|
|
38
|
+
# Create HTTP session
|
|
39
|
+
self.session = httpx.AsyncClient()
|
|
40
|
+
|
|
41
|
+
# Set base URL from the servers list if available
|
|
42
|
+
self.setup_base_url()
|
|
43
|
+
|
|
44
|
+
# Create a dynamic client with methods based on the operations defined in the spec
|
|
45
|
+
return await self.create_dynamic_client()
|
|
46
|
+
|
|
47
|
+
async def load_definition(self):
|
|
48
|
+
"""
|
|
49
|
+
Load the OpenAPI definition from a URL, file, or dictionary.
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(self.definition_source, dict):
|
|
52
|
+
self.definition = self.definition_source
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if os.path.isfile(str(self.definition_source)):
|
|
56
|
+
# Load from file
|
|
57
|
+
with open(self.definition_source, 'r') as f:
|
|
58
|
+
content = f.read()
|
|
59
|
+
if self.definition_source.endswith('.yaml') or self.definition_source.endswith('.yml'):
|
|
60
|
+
self.definition = yaml.safe_load(content)
|
|
61
|
+
else:
|
|
62
|
+
self.definition = json.loads(content)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Assume it's a URL
|
|
66
|
+
self.source_url = self.definition_source # Store the source URL
|
|
67
|
+
async with httpx.AsyncClient() as client:
|
|
68
|
+
response = await client.get(self.definition_source)
|
|
69
|
+
if response.status_code == 200:
|
|
70
|
+
content_type = response.headers.get('Content-Type', '')
|
|
71
|
+
if 'yaml' in content_type or 'yml' in content_type:
|
|
72
|
+
self.definition = yaml.safe_load(response.text)
|
|
73
|
+
else:
|
|
74
|
+
self.definition = response.json()
|
|
75
|
+
else:
|
|
76
|
+
raise Exception(f"Failed to load OpenAPI definition: {response.status_code}")
|
|
77
|
+
|
|
78
|
+
def setup_base_url(self):
|
|
79
|
+
"""
|
|
80
|
+
Set up the base URL for API requests, handling various server URL formats.
|
|
81
|
+
"""
|
|
82
|
+
if 'servers' in self.definition and self.definition['servers']:
|
|
83
|
+
server_url = self.definition['servers'][0]['url']
|
|
84
|
+
|
|
85
|
+
# Check if this is a full URL or just a path
|
|
86
|
+
parsed_url = urlparse(server_url)
|
|
87
|
+
|
|
88
|
+
# If it's a full URL (has scheme), use it directly
|
|
89
|
+
if parsed_url.scheme:
|
|
90
|
+
self.base_url = server_url
|
|
91
|
+
# If it's not a full URL and we loaded from a URL, combine them
|
|
92
|
+
elif self.source_url:
|
|
93
|
+
# Parse the source URL to get scheme, hostname, and port
|
|
94
|
+
source_parsed = urlparse(self.source_url)
|
|
95
|
+
base = f"{source_parsed.scheme}://{source_parsed.netloc}"
|
|
96
|
+
|
|
97
|
+
# Combine the base with the server path
|
|
98
|
+
self.base_url = urljoin(base, server_url)
|
|
99
|
+
else:
|
|
100
|
+
# Just use what we have
|
|
101
|
+
self.base_url = server_url
|
|
102
|
+
|
|
103
|
+
async def create_dynamic_client(self):
|
|
104
|
+
"""
|
|
105
|
+
Create a client with methods dynamically generated from the OpenAPI spec using metaprogramming.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
DynamicClient: A client with methods for each operation in the spec
|
|
109
|
+
"""
|
|
110
|
+
# Create a new class dynamically using type
|
|
111
|
+
methods_dict = {}
|
|
112
|
+
|
|
113
|
+
# Generate methods for each path and operation
|
|
114
|
+
paths = self.definition.get('paths', {})
|
|
115
|
+
for path, path_item in paths.items():
|
|
116
|
+
for method, operation in path_item.items():
|
|
117
|
+
if method in ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']:
|
|
118
|
+
operation_id = operation.get('operationId')
|
|
119
|
+
if operation_id:
|
|
120
|
+
# Create a method for this operation and capture it in the closure
|
|
121
|
+
method_func = self.create_operation_method(path, method, operation)
|
|
122
|
+
|
|
123
|
+
# Create a function with proper binding
|
|
124
|
+
def create_bound_method(func=method_func):
|
|
125
|
+
async def bound_method(*args, **kwargs):
|
|
126
|
+
return await func(*args, **kwargs)
|
|
127
|
+
# Set the name and docstring
|
|
128
|
+
bound_method.__name__ = operation_id
|
|
129
|
+
bound_method.__doc__ = operation.get('summary', '') + "\n\n" + operation.get('description', '')
|
|
130
|
+
return bound_method
|
|
131
|
+
|
|
132
|
+
methods_dict[operation_id] = create_bound_method()
|
|
133
|
+
|
|
134
|
+
# Create the dynamic client class with the methods
|
|
135
|
+
DynamicClientClass = type('DynamicClient', (object,), methods_dict)
|
|
136
|
+
|
|
137
|
+
# Create an instance of this class
|
|
138
|
+
client = DynamicClientClass()
|
|
139
|
+
|
|
140
|
+
# Store reference to the api
|
|
141
|
+
client._api = self
|
|
142
|
+
|
|
143
|
+
return client
|
|
144
|
+
|
|
145
|
+
def create_operation_method(self, path, method, operation):
|
|
146
|
+
"""
|
|
147
|
+
Create a method for an operation defined in the OpenAPI spec.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
path: The path template (e.g., "/pets/{petId}")
|
|
151
|
+
method: The HTTP method (e.g., "get", "post")
|
|
152
|
+
operation: The operation object from the OpenAPI spec
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
function: A method that performs the API request
|
|
156
|
+
"""
|
|
157
|
+
async def operation_method(*args, **kwargs):
|
|
158
|
+
# Process path parameters
|
|
159
|
+
url = path
|
|
160
|
+
path_params = {}
|
|
161
|
+
|
|
162
|
+
# Extract parameters from operation definition
|
|
163
|
+
parameters = operation.get('parameters', [])
|
|
164
|
+
for param in parameters:
|
|
165
|
+
if param.get('in') == 'path':
|
|
166
|
+
name = param.get('name')
|
|
167
|
+
if name in kwargs:
|
|
168
|
+
path_params[name] = kwargs.pop(name)
|
|
169
|
+
|
|
170
|
+
# Replace path parameters in the URL
|
|
171
|
+
for name, value in path_params.items():
|
|
172
|
+
url = url.replace(f"{{{name}}}", str(value))
|
|
173
|
+
|
|
174
|
+
# Build the full URL
|
|
175
|
+
full_url = urljoin(self.base_url, url)
|
|
176
|
+
|
|
177
|
+
# Handle query parameters
|
|
178
|
+
query_params = {}
|
|
179
|
+
for param in parameters:
|
|
180
|
+
if param.get('in') == 'query':
|
|
181
|
+
name = param.get('name')
|
|
182
|
+
if name in kwargs:
|
|
183
|
+
query_params[name] = kwargs.pop(name)
|
|
184
|
+
|
|
185
|
+
# Handle request body
|
|
186
|
+
body = kwargs.pop('data', None)
|
|
187
|
+
|
|
188
|
+
# Make the request
|
|
189
|
+
headers = kwargs.pop('headers', {})
|
|
190
|
+
|
|
191
|
+
response = await self.session.request(
|
|
192
|
+
method,
|
|
193
|
+
full_url,
|
|
194
|
+
params=query_params,
|
|
195
|
+
json=body,
|
|
196
|
+
headers=headers,
|
|
197
|
+
**kwargs
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if 'application/json' in response.headers.get('Content-Type', ''):
|
|
201
|
+
result = response.json()
|
|
202
|
+
else:
|
|
203
|
+
result = response.text
|
|
204
|
+
|
|
205
|
+
# Create response object similar to axios
|
|
206
|
+
return {
|
|
207
|
+
'data': result,
|
|
208
|
+
'status': response.status_code,
|
|
209
|
+
'headers': dict(response.headers),
|
|
210
|
+
'config': kwargs
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return operation_method
|
|
214
|
+
|
|
215
|
+
async def close(self):
|
|
216
|
+
"""Close the HTTP session."""
|
|
217
|
+
if self.session:
|
|
218
|
+
await self.session.aclose()
|
|
219
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="openapi-httpx-client",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="lloydzhou",
|
|
10
|
+
author_email="lloydzhou@qq.com",
|
|
11
|
+
description="A Python client for OpenAPI specifications using httpx",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/lloydzhou/openapiclient",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
install_requires=[
|
|
17
|
+
"httpx>=0.23.0",
|
|
18
|
+
"pyyaml>=6.0",
|
|
19
|
+
],
|
|
20
|
+
classifiers=[
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
],
|
|
25
|
+
python_requires=">=3.7",
|
|
26
|
+
)
|
|
27
|
+
|