openapi-httpx-client 0.4.2__tar.gz → 0.4.3__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Lloyd Zhou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,17 +1,28 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: openapi-httpx-client
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: A Python client for OpenAPI specifications using httpx
5
5
  Home-page: https://github.com/lloydzhou/openapiclient
6
6
  Author: lloydzhou
7
7
  Author-email: lloydzhou@qq.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Programming Language :: Python :: 3
11
9
  Classifier: License :: OSI Approved :: MIT License
12
10
  Classifier: Operating System :: OS Independent
13
11
  Requires-Python: >=3.7
14
12
  Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: httpx>=0.23.0
15
+ Requires-Dist: pyyaml>=6.0
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
15
26
 
16
27
  # OpenAPI Client for Python
17
28
 
@@ -132,5 +143,3 @@ All API responses are returned in a dictionary format with the following keys:
132
143
  lloydzhou
133
144
 
134
145
 
135
-
136
-
@@ -1,17 +1,28 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: openapi-httpx-client
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: A Python client for OpenAPI specifications using httpx
5
5
  Home-page: https://github.com/lloydzhou/openapiclient
6
6
  Author: lloydzhou
7
7
  Author-email: lloydzhou@qq.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Programming Language :: Python :: 3
11
9
  Classifier: License :: OSI Approved :: MIT License
12
10
  Classifier: Operating System :: OS Independent
13
11
  Requires-Python: >=3.7
14
12
  Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: httpx>=0.23.0
15
+ Requires-Dist: pyyaml>=6.0
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
15
26
 
16
27
  # OpenAPI Client for Python
17
28
 
@@ -132,5 +143,3 @@ All API responses are returned in a dictionary format with the following keys:
132
143
  lloydzhou
133
144
 
134
145
 
135
-
136
-
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  setup.py
3
4
  openapi_httpx_client.egg-info/PKG-INFO
@@ -3,6 +3,8 @@ import json
3
3
  import os.path
4
4
  from urllib.parse import urljoin, urlparse
5
5
  import yaml
6
+ import re
7
+
6
8
 
7
9
  # 合并DynamicClientBase和BaseClient为一个基类
8
10
  class BaseClient:
@@ -67,13 +69,13 @@ class Client(BaseClient):
67
69
  def __init__(self, api, **kwargs):
68
70
  """Initialize the sync client"""
69
71
  super().__init__(api)
70
- self.session = httpx.Client(**kwargs)
72
+ self.session = api.httpx_client or httpx.Client(**kwargs)
71
73
 
72
74
  def __enter__(self):
73
75
  """Enter context manager and initialize the client"""
74
76
  if not self.api.definition:
75
77
  self.api._load_definition_sync()
76
-
78
+
77
79
  self.setup_base_url()
78
80
  # Generate methods directly on this instance
79
81
  self.api._generate_client_methods(self, is_async=False)
@@ -94,7 +96,7 @@ class AsyncClient(BaseClient):
94
96
  def __init__(self, api, **kwargs):
95
97
  """Initialize the async client"""
96
98
  super().__init__(api)
97
- self.session = httpx.AsyncClient(**kwargs)
99
+ self.session = api.httpx_async_client or httpx.AsyncClient(**kwargs)
98
100
 
99
101
  async def __aenter__(self):
100
102
  """Enter async context manager and initialize the client"""
@@ -112,6 +114,50 @@ class AsyncClient(BaseClient):
112
114
  await self.session.aclose()
113
115
 
114
116
 
117
+ def sanitize_openapi_path(path: str) -> str:
118
+ """
119
+ Convert OpenAPI path (e.g. "/v3/symbols/{symbol}/session") into a valid Python identifier.
120
+ """
121
+
122
+ # replace path parameters {param} with sanitized param name prefixed with "by_"
123
+ def repl_param(m):
124
+ name = m.group(1) or ""
125
+ # sanitize param name: replace non-alnum/_ with underscore and collapse
126
+ name = re.sub(r"[^0-9A-Za-z_]", "_", name)
127
+ name = re.sub(r"_+", "_", name).strip("_")
128
+ return "by_" + name
129
+
130
+ s = re.sub(r"\{([^}]*)\}", repl_param, path)
131
+ # replace slashes/backslashes with underscore
132
+ s = re.sub(r"[\\/]+", "_", s)
133
+ # replace any remaining non-alnum/_ with underscore
134
+ s = re.sub(r"[^0-9A-Za-z_]", "_", s)
135
+ # collapse underscores and strip edges
136
+ s = re.sub(r"_+", "_", s).strip("_")
137
+
138
+ return s
139
+
140
+
141
+ def resolve_open_api_reference(dct, definition):
142
+ if "$ref" not in dct:
143
+ return dct
144
+ else:
145
+ ref_path = dct["$ref"]
146
+ # Only handling local references for simplicity
147
+ if not ref_path.startswith("#/"):
148
+ raise NotImplementedError(
149
+ "Only local references are supported in this implementation."
150
+ )
151
+
152
+ parts = ref_path.lstrip("#/").split("/")
153
+ ref = definition
154
+ for part in parts:
155
+ ref = ref.get(part)
156
+ if ref is None:
157
+ raise ValueError(f"Reference {ref_path} could not be resolved.")
158
+ return ref
159
+
160
+
115
161
  # Create the main OpenAPIClient class as a factory
116
162
  class OpenAPIClient:
117
163
  """
@@ -130,7 +176,7 @@ class OpenAPIClient:
130
176
  result = await client.operation_name(param1=value)
131
177
  """
132
178
 
133
- def __init__(self, definition=None):
179
+ def __init__(self, definition=None, httpx_client=None, httpx_async_client=None):
134
180
  """
135
181
  Initialize the OpenAPI client.
136
182
 
@@ -139,8 +185,10 @@ class OpenAPIClient:
139
185
  """
140
186
  self.definition_source = definition
141
187
  self.definition = {}
142
- self.base_url = ''
188
+ self.base_url = ""
143
189
  self.source_url = None # Store the source URL if loaded from a URL
190
+ self.httpx_client = httpx_client
191
+ self.httpx_async_client = httpx_async_client
144
192
 
145
193
  def Client(self, **kwargs):
146
194
  """
@@ -153,7 +201,7 @@ class OpenAPIClient:
153
201
  Client: A synchronous client
154
202
  """
155
203
  return Client(self, **kwargs)
156
-
204
+
157
205
  def AsyncClient(self, **kwargs):
158
206
  """
159
207
  Create an asynchronous client instance that can be used as a context manager.
@@ -353,7 +401,9 @@ class OpenAPIClient:
353
401
  for operation in self.get_operations():
354
402
  operation_id = operation.get('operationId')
355
403
  if not operation_id:
356
- continue
404
+ operation_id = (
405
+ operation["method"] + "_" + sanitize_openapi_path(operation["path"])
406
+ )
357
407
 
358
408
  path = operation.get('path')
359
409
  paths.append(path)
@@ -399,6 +449,7 @@ class OpenAPIClient:
399
449
  # Extract parameters from operation definition
400
450
  parameters = operation.get('parameters', [])
401
451
  for param in parameters:
452
+ param = resolve_open_api_reference(param, self.definition)
402
453
  if param.get('in') == 'path':
403
454
  name = param.get('name')
404
455
  if name in kwargs:
@@ -416,6 +467,7 @@ class OpenAPIClient:
416
467
  # Handle query parameters
417
468
  query_params = {}
418
469
  for param in parameters:
470
+ param = resolve_open_api_reference(param, self.definition)
419
471
  if param.get('in') == 'query':
420
472
  name = param.get('name')
421
473
  if name in kwargs:
@@ -483,10 +535,10 @@ class OpenAPIClient:
483
535
  response = await client_instance.session.request(
484
536
  method,
485
537
  full_url,
486
- params=query_params,
487
- json=body,
488
- headers=headers,
489
- **remaining_kwargs
538
+ params=query_params,
539
+ json=body,
540
+ headers=dict(client_instance.session.headers) | headers,
541
+ **remaining_kwargs,
490
542
  )
491
543
 
492
544
  # Process the response
@@ -502,10 +554,10 @@ class OpenAPIClient:
502
554
  response = client_instance.session.request(
503
555
  method,
504
556
  full_url,
505
- params=query_params,
506
- json=body,
507
- headers=headers,
508
- **remaining_kwargs
557
+ params=query_params,
558
+ json=body,
559
+ headers=dict(client_instance.session.headers) | headers,
560
+ **remaining_kwargs,
509
561
  )
510
562
 
511
563
  # Process the response
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name="openapi-httpx-client",
8
- version="0.4.2",
8
+ version="0.4.3",
9
9
  author="lloydzhou",
10
10
  author_email="lloydzhou@qq.com",
11
11
  description="A Python client for OpenAPI specifications using httpx",