Add request partitioning to waterdata_client. - #325
Conversation
waterdata_client.waterdata_client.
christophertubbs
left a comment
There was a problem hiding this comment.
I identified an anti-pattern and some subjective nit-picks. It looks good enough to ship, so there's nothing blocking it.
Consider some of the comments in further development. Consider opening the pathway (without necessarily implementing it) to partition by other fields or multiple fields.
| ) | ||
| queries = [] | ||
| for group in grouped_list: | ||
| q = deepcopy(base_query) |
There was a problem hiding this comment.
q is not a sufficient variable name, especially in our domain ("What is discharge doing here?"). Add a ticket for hunting down and replacing these types of variable definitions. Something like ^\s*([a-zA-Z]\s*(:([^=])*)?=.+)$ may help you find everything in the search bar in vscode or pycharm.
|
|
||
| # Ignore None | ||
| return {k: v for k, v in query.items() if v is not None} | ||
| return [{k: v for k, v in base_query.items() if v is not None}] |
There was a problem hiding this comment.
Single character variable names don't necessarily need to follow the full naming rules so it doesn't need to follow the rule stated above. Nest a deeper comprehension, however, and naming rules come back into play.
| ) | ||
| queries = [] | ||
| for group in grouped_list: | ||
| q = deepcopy(base_query) |
There was a problem hiding this comment.
deepcopy is a bit over the top for a shallow dict. The following may work "better" for partitioning:
for group in grouped_list:
query = {
**{
key: value
for key, value in base_query.items()
if value is not None
},
"monitoring_location_id": group
}
queries.append(query)I wouldn't further compress the immediately prior logic since it'll add unnecessary cognitive overhead.
It may be worth putting the following line above the partitioning logic:
base_query = {k: v for k, v in query.items() if v is not None}and the following at the end:
return [base_query]you may also want to consider invalid empty values in the filter for None values. A base_query value of "properties": {} may cause some heart ache either now or if there is a change later down the line with unfortunate and unexpected side effects.
Don't consider this a MUST FIX - some of it is a subjective preference and what is provided seems like it'll work. This all may be worth a low-hanging-fruit ticket.
| if n < 1: | ||
| raise ValueError("n must be at least one") | ||
| iterator = iter(iterable) | ||
| while batch := tuple(islice(iterator, n)): |
There was a problem hiding this comment.
Another subjective comment: consider reformatting as:
batch = tuple(islice(iterator, n))
while batch:
if strict and len(batch) !=n:
raise ValueError("batched(): incomplete batch")
yield batch
batch = tuple(islice(iterator, n))or
while True:
batch = tuple(islice(iterator, n))
if not batch:
break
if strict and len(batch) != n:
raise ValueError("batched(): incomplete batch")
yield batchWhile Python added := to make it easier to do what you're doing, you're also performing a mutation in your conditional check. It's a classic no-no (outside of a for-loop). The typical names for that anti-pattern are hidden side effect or hidden mutation.
@christophertubbs Thanks for the review. I'll add "assessment and mitigation of anti-patterns" to the robustness ticket. I was originally going to attempt a more general partitioning solution, however, it was a bit out of scope for this particular development cycle. I figured getting a site-based partitioning scheme was a good place to start. I do think the necessary elements are present to expand partitioning to other arguments (query intervention, request model expansion, and a chunk parameter). |
This PR adds location-based request partitioning to the
waterdata_clientand brings the client into beta with version0.9.0b0. The primary client interfaces are stable and no new features will be added until after the first production/stable release (1.0.0). A roadmap forv1.0.0is forthcoming.Changes
scripts/build_clients.pyAdded newStringListtype formonitoring_location_idarguments.scripts/build_request_models.pyAdded newStringListtype formonitoring_location_idarguments.clients/*.pyRegenerated all client modules.request_models/*.pyRegenerated all request model modules.templates/clients.py.j2Includes new Jinja2 boolean namespace variable calledlocation_id. This triggers the addition of the newStringListtype and additional logic for handlingmonitoring_location_idwhen generating queries passed toget_all. Also accommodates changes torequest_models.py.j2.templates/request_models.py.j2Includes new Jinja2 boolean namespace variable calledlocation_id. This triggers the addition of the newStringListtype and additional logic for handlingmonitoring_location_idwhen generating queries passed toget_all. The additional logic uses a new method calledgroup_string_iterableto chunk a list of string into comma-delimited lists compatible with WaterData APIs._version.pyBumped to0.9.0b0base_client.pyAdds newchunk_sizeattribute toBaseClient.client_config.pyAdds newchunk_sizeattribute to package-wideSETTINGSand new environment variableHYDROTOOLS_CHUNK_SIZE.request_paritioning.pyNew module with three new methods.batchedis a python 3.11 friendly implementation of theitertools.batchedmethod added in python 3.12. The python 3.12 implementation is written in C and theoretically faster, so the module imports that if available.group_string_iterabletakes astr | Iterable[str]and returns a list ofstrwhere the elements are chunked comma-delimited lists of items from the originalIterable.validate_sequence_argumentis a Pydantic validation function used with the newStringListtype that makesmonitoring_location_idalist[str].Testing
tests/test_clients.pyAdded tests that clients correctly pull defaultchunk_sizefrom package-wideSETTINGS, allowing override at time of call. Checks for correct number of unique URLs with correctly added list of locations.tests/test_iterable_conversion.pyNew tests for Pydantic models that use the new partitioning logic. Includes defaults, overrides, query generation, and correctly raisedpydantic.ValidationError.tests/test_partitioning.pyNew tests of underlying stand-alone partitioning methods.Checklist