refactor: write api and rest client#217
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the write path to use a new synchronous RestClient (urllib3-based) instead of the previous generated client/write service wiring, and updates InfluxDBClient3 construction accordingly.
Changes:
- Refactors
WriteApito issue HTTP requests via a newRestClient, adds gzip decision/compression utilities, and adds endpoint/exception translation logic. - Introduces
influxdb_client_3/write_client/_sync/rest_client.pyto encapsulate urllib3 request/response handling. - Updates
InfluxDBClient3initialization to pass write connection details (base_url/auth/gzip) directly intoWriteApi.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 15 comments.
| File | Description |
|---|---|
influxdb_client_3/write_client/client/write_api.py |
Major refactor of write implementation to use a new REST client and custom request building/serialization paths. |
influxdb_client_3/write_client/_sync/rest_client.py |
Adds a new urllib3-based synchronous REST client abstraction used by WriteApi. |
influxdb_client_3/__init__.py |
Wires new write API constructor parameters (auth/gzip/base_url) and updates public kwargs documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2b26e5c to
7d74438
Compare
52cee20 to
a7076f9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 12 comments.
Comments suppressed due to low confidence (2)
tests/test_write_api.py:251
WriteApi.write()takes parameters in(bucket, org, record, ...)order, but this test passes(org, bucket, record). This makes the test less representative of real usage and can mask issues in how query parameters are constructed.
tests/test_write_api.py:293- This call passes
(org, bucket, record)instead of(bucket, org, record), which makes the timeout test less representative of real usage and inconsistent with other tests in this file.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
influxdb_client_3/write_client/client/write_api.py:1171
__getstate__deletes_write_service, butWriteApino longer defines that attribute. This will raiseKeyErrorduring pickling (anddel state['_subject']/del state['_disposable']can also KeyError depending on instance state). Usestate.pop(..., None)and remove the stale_write_servicereference.
def __getstate__(self):
"""Return a dict of attributes that you want to pickle."""
state = self.__dict__.copy()
# Remove rx
del state['_subject']
del state['_disposable']
del state['_write_service']
return state
influxdb_client_3/write_client/client/write_api.py:1181
__setstate__callsself.__init__(self._write_options, self._point_settings, ...), butWriteApi.__init__requirestoken,bucket, andorgas the first arguments. As written, unpickling will raiseTypeError. Recreate the Rx batching pipeline based on the restored_write_optionsinstead of re-calling__init__with the wrong signature.
def __setstate__(self, state):
"""Set your object with the provided dict."""
self.__dict__.update(state)
# Init Rx
self.__init__(self._write_options,
self._point_settings,
success_callback=self._success_callback,
error_callback=self._error_callback,
retry_callback=self._retry_callback)
tests/test_write_api.py:252
WriteApi.write()expects positional arguments in the order(bucket, org, record). This call passes org first, which swaps the query parameters and makes the test less representative of real usage. Use keyword arguments (or correct positional order) to avoid accidental swaps.
tests/test_write_api.py:293WriteApi.write()positional argument order is(bucket, org, record). This test passes org then bucket, which is easy to get wrong and can hide routing/query-param bugs. Prefer keyword arguments to make the intent explicit (and keep_request_timeoutbehavior under test).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (5)
tests/test_write_api.py:29
mock_urllib3_timeout_requestis used as aside_effectforurllib3._request_methods.RequestMethods.request, which passes theRequestMethodsinstance as the first positional arg. The helper currently omits that parameter, somethod/urlare shifted and the test can behave incorrectly.
tests/test_write_api.py:252WriteApi.write()takesbucketas the first positional argument andorgas the second. This call passes them in the opposite order, so the request routing/query params will be wrong.
tests/test_write_api.py:293WriteApi.write()takesbucketas the first positional argument andorgas the second. This call passes them in the opposite order, so it won't exercise the intended timeout behavior for the right endpoint params.
influxdb_client_3/write_client/client/write_api.py:1171__getstate__deletesstate['_write_service'], but this class no longer defines_write_service. Pickling will raiseKeyErrorhere.
state = self.__dict__.copy()
# Remove rx
del state['_subject']
del state['_disposable']
del state['_write_service']
return state
influxdb_client_3/write_client/client/write_api.py:1181
__setstate__callsself.__init__(self._write_options, self._point_settings, ...), but__init__now requirestoken,bucket,org, and other constructor args. This will break unpickling; it should just recreate the Rx batching pipeline based on the restored_write_options.
def __setstate__(self, state):
"""Set your object with the provided dict."""
self.__dict__.update(state)
# Init Rx
self.__init__(self._write_options,
self._point_settings,
success_callback=self._success_callback,
error_callback=self._error_callback,
retry_callback=self._retry_callback)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
tests/test_write_api.py:252
WriteApi.write()takes positional arguments in the order(bucket, org, record). This call passes org first and bucket second, which swaps routing parameters and can hide bugs in header/query handling. Prefer keyword arguments (or swap the positionals) to make the intent unambiguous.
tests/test_write_api.py:293- Same positional-argument issue as above: this passes org first and bucket second to
WriteApi.write(bucket, org, record), which inverts query parameters. Using keywords also makes the_request_timeoutintent clearer.
influxdb_client_3/write_client/client/write_api.py:1181 __getstate__/__setstate__are broken after the refactor:_write_serviceis no longer an attribute, sodel state['_write_service']will raiseKeyErrorduring pickling. Additionally,__setstate__calls__init__with the wrong arguments for the new constructor signature (it no longer accepts justwrite_optionsandpoint_settings). This makes pickling/unpickling unusable.
def __getstate__(self):
"""Return a dict of attributes that you want to pickle."""
state = self.__dict__.copy()
# Remove rx
del state['_subject']
del state['_disposable']
del state['_write_service']
return state
def __setstate__(self, state):
"""Set your object with the provided dict."""
self.__dict__.update(state)
# Init Rx
self.__init__(self._write_options,
self._point_settings,
success_callback=self._success_callback,
error_callback=self._error_callback,
retry_callback=self._retry_callback)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
influxdb_client_3/write_client/client/write_api.py:1181
__getstate__deletesstate['_write_service'], butWriteApino longer defines_write_service, so pickling will raiseKeyError. Additionally,__setstate__calls__init__with the wrong signature (it now requirestoken,bucket,org, etc.), so unpickling will fail even if__getstate__succeeds. Safer approach:pop()optional fields and recreate the Rx pipeline based on_write_optionswithout re-calling__init__.
def __getstate__(self):
"""Return a dict of attributes that you want to pickle."""
state = self.__dict__.copy()
# Remove rx
del state['_subject']
del state['_disposable']
del state['_write_service']
return state
def __setstate__(self, state):
"""Set your object with the provided dict."""
self.__dict__.update(state)
# Init Rx
self.__init__(self._write_options,
self._point_settings,
success_callback=self._success_callback,
error_callback=self._error_callback,
retry_callback=self._retry_callback)
tests/test_write_api.py:252
WriteApi.write()expects positional arguments as(bucket, org, record, ...), but this test passes them as(org, bucket, record). That swaps query params and makes the test exercise the wrong behavior. Use keyword args (or correct positional order) to match thewrite()signature.
tests/test_write_api.py:294- This
write()call also swaps(org, bucket)positional order. Since the assertion is about timeout propagation rather than parameter order, using keyword args avoids accidentally testing the wrong thing.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
tests/test_write_api.py:252
- In this test,
WriteApi.write()expects positional args in the order(bucket, org, record). Passing org first swaps the routing parameters and can hide bugs in request construction/error handling.
tests/test_write_api.py:293 WriteApi.write()positional parameters are(bucket, org, record). This call passes org first, which makes the test exercise the wrong request routing.
87ccdef to
273b4ce
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (3)
influxdb_client_3/write_client/client/write_api.py:1179
__getstate__()deletes_write_service, butWriteApino longer sets this attribute, so pickling will raiseKeyError. Additionally,__setstate__()calls__init__()with the wrong signature (it now requirestoken/bucket/org/...).
def __getstate__(self):
"""Return a dict of attributes that you want to pickle."""
state = self.__dict__.copy()
# Remove rx
del state['_subject']
del state['_disposable']
del state['_write_service']
return state
def __setstate__(self, state):
"""Set your object with the provided dict."""
self.__dict__.update(state)
# Init Rx
self.__init__(self._write_options,
self._point_settings,
success_callback=self._success_callback,
error_callback=self._error_callback,
retry_callback=self._retry_callback)
tests/test_write_api.py:252
- Argument order is swapped in this
write()call:WriteApi.write(bucket, org, record, ...)expects bucket first, org second, but this passes org then bucket. This makes the test less meaningful and can hide real regressions.
tests/test_write_api.py:293 - Argument order is swapped in this
write()call:WriteApi.write(bucket, org, record, ...)expects bucket first, org second. As written, the test exercises the wrong parameter mapping (and still "works" only because values are strings).
|
Hi guys, based on Jakub's new comments, I have tried to work on the refactor of rest client first then working on Multiprocessing help class later. |
bednar
left a comment
There was a problem hiding this comment.
I found two remaining issues around pickle support.
|
@bednar |
bednar
left a comment
There was a problem hiding this comment.
One batching pickle regression remains.
bednar
left a comment
There was a problem hiding this comment.
Two additional backward-compatibility requirements remain.
bednar
left a comment
There was a problem hiding this comment.
The changelog needs to describe the public compatibility impact of this refactor.
| org = 'my-org' | ||
| writer = MultiprocessingWriter( | ||
| host=self.host, | ||
| database=self.database, | ||
| token=self.token, | ||
| org=org, | ||
| write_options=WriteOptions(batch_size=1)) | ||
| writer.start() | ||
| measurement = f'test{random_hex(6)}'.lower() | ||
| for x in range(1, 10): | ||
| time.sleep(0.2) | ||
| writer.write( | ||
| bucket=self.database, | ||
| record=f"{measurement},tag=a value=\"number{x}\" {time.time_ns()}" | ||
| ) | ||
| writer.__del__() | ||
|
|
There was a problem hiding this comment.
Will fix this later.
There was a problem hiding this comment.
N.B. this test is currently disabled in the standard CI pipeline. It will be updated in #222. So, for this PR this item can be for the short term ignored.
| self.write_api.__del__() | ||
| self.write_api = None |
There was a problem hiding this comment.
Will fix this later.
bednar
left a comment
There was a problem hiding this comment.
Two debug compatibility issues remain in the new RestClient.
|
bednar
left a comment
There was a problem hiding this comment.
The breaking changes entry needs migration guidance.
| r = RESTResponse(r) | ||
| if isinstance(r.data, (bytes, bytearray)): | ||
| r.data = r.data.decode('utf-8') | ||
| elif r.data is None: | ||
| r.data = '' | ||
|
|
There was a problem hiding this comment.
Is there a use case for returning raw bytes from this client? Can this comment be addressed?
karel-rehor
left a comment
There was a problem hiding this comment.
Still in progress, but before I leave at COB, I already have one minor request, which is also related to some unit tests.
karel-rehor
left a comment
There was a problem hiding this comment.
I still have 4 files to review. But in the mean time, I've raised a few some more change requests. Most of them involve cleaner documentation or argument naming.
| r = RESTResponse(r) | ||
| if isinstance(r.data, (bytes, bytearray)): | ||
| r.data = r.data.decode('utf-8') | ||
| elif r.data is None: | ||
| r.data = '' | ||
|
|
There was a problem hiding this comment.
Is there a use case for returning raw bytes from this client? Can this comment be addressed?
|
Hi @karel-rehor |
karel-rehor
left a comment
There was a problem hiding this comment.
OK, looks good to me now.
- Concerns related to
multiprocessing_helper.pyare being addressed in #222 - All tests now pass locally on my Linux machine.
Good to go. 🚴 🏁
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
influxdb_client_3/init.py:368
query_port_overwritecurrently mutates the sharedportvariable that was already overridden bywrite_port_overwrite. As a result, if a caller sets onlywrite_port_overwrite, the gRPC query connection string will also use the write port (even though a separatequery_port_overwriteparameter exists). This makes the write-port override unintentionally affect query routing.
if query_port_overwrite is not None:
port = query_port_overwrite
if scheme == 'https':
connection_string = f"grpc+tls://{hostname}:{port}"
else:
connection_string = f"grpc+tcp://{hostname}:{port}"
| from influxdb_client_3.write_client.write_exceptions import ApiException | ||
| from tests.util import asyncio_run, lp_to_py_object | ||
|
|
||
| running_on_posix = os.name == 'posix' |
| list_results = reader.to_pylist() | ||
| self.assertEqual(data_size, len(list_results)) | ||
|
|
||
| @pytest.mark.skipif(running_on_posix, reason="Skipping this test in POSIX environments") |
|
Hi @karel-rehor |
Closes #216
Proposed Changes
Summarize
Configuration,ApiClient,WriteService,InfluxDBClientclasses.Removed features:
Checklist