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.
@@ -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,2 @@
1
+ httpx>=0.23.0
2
+ pyyaml>=6.0
@@ -0,0 +1,3 @@
1
+ from .client import OpenAPIClient
2
+
3
+ __all__ = ['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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+