Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate root StacAPIIO to ItemSearch in Client.get_items #126

Merged
merged 4 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]

### Fixed
- Values from `parameters` and `headers` arguments to `Client.open` and `Client.from_file` are now also used in requests made from `CollectionClient` instances
fetched from the same API ([#126](https://github.com/stac-utils/pystac-client/pull/126))
- The tests folder is no longer installed as a package.

## [0.3.1] - 2021-11-17
Expand Down
2 changes: 1 addition & 1 deletion pystac_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def get_collection(self, collection_id: str) -> CollectionClient:
"""
if self._stac_io.conforms_to(ConformanceClasses.COLLECTIONS):
url = f"{self.get_self_href()}/collections/{collection_id}"
collection = CollectionClient.from_dict(self._stac_io.read_json(url))
collection = CollectionClient.from_dict(self._stac_io.read_json(url), root=self)
return collection
else:
for col in self.get_collections():
Expand Down
2 changes: 1 addition & 1 deletion pystac_client/collection_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get_items(self) -> Iterable["Item_Type"]:

link = self.get_single_link('items')
if link is not None:
search = ItemSearch(link.href, method='GET')
search = ItemSearch(link.href, method='GET', stac_io=self.get_root()._stac_io)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing technically wrong here, but accessing a "private" member of an object retrieved via a function call feels like we're breaking abstraction. This isn't surprising given the nature of how the StacIO stuff has to work, but I'd just flag this for a bit of code smell w/ maybe the chance to refactor sometime down the line. No changes required now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks for flagging that. I had originally added a "public" stac_io property to pystac.Catalog in stac-utils/pystac#590, but we ultimately decided that it wasn't needed as part of that PR, so I took it back out. Given that there are a couple of places where it is accessed in the pystac-client code, it might be worth revisiting that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened stac-utils/pystac#675 to discuss adding a "public" attribute/property.

yield from search.get_items()
else:
yield from super().get_items()
105 changes: 105 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,111 @@ def test_custom_request_parameters(self, requests_mock):
assert len(actual_qp[init_qp_name]) == 1
assert actual_qp[init_qp_name][0] == init_qp_value

def test_custom_query_params_get_collections_propagation(self, requests_mock) -> None:
"""Checks that query params passed to the init method are added to requests for CollectionClients fetched from
the /collections endpoint."""
pc_root_text = read_data_file("planetary-computer-root.json")
pc_collection_dict = read_data_file("planetary-computer-collection.json", parse_json=True)

requests_mock.get(STAC_URLS["PLANETARY-COMPUTER"], status_code=200, text=pc_root_text)

init_qp_name = "my-param"
init_qp_value = "some-value"

client = Client.open(STAC_URLS['PLANETARY-COMPUTER'],
parameters={init_qp_name: init_qp_value})

# Get the /collections endpoint
collections_link = client.get_single_link("data")

# Mock the request
requests_mock.get(collections_link.href,
status_code=200,
json={
"collections": [pc_collection_dict],
"links": []
})

# Make the collections request
collection = next(client.get_collections())

# Mock the items endpoint
items_link = collection.get_single_link('items')
assert items_link is not None
requests_mock.get(items_link.href,
status_code=200,
json={
"type": "FeatureCollection",
"stac_version": "1.0.0",
"features": [],
"links": []
})

# Make the items request
_ = list(collection.get_items())

history = requests_mock.request_history
assert len(history) == 3

actual_qs = urlsplit(history[2].url).query
actual_qp = parse_qs(actual_qs)

# Check that the query param from the root Client is present
assert init_qp_name in actual_qp
assert len(actual_qp[init_qp_name]) == 1
assert actual_qp[init_qp_name][0] == init_qp_value

def test_custom_query_params_get_collection_propagation(self, requests_mock) -> None:
"""Checks that query params passed to the init method are added to requests for CollectionClients fetched from
the /collections endpoint."""
pc_root_text = read_data_file("planetary-computer-root.json")
pc_collection_dict = read_data_file("planetary-computer-collection.json", parse_json=True)
pc_collection_id = pc_collection_dict["id"]

requests_mock.get(STAC_URLS["PLANETARY-COMPUTER"], status_code=200, text=pc_root_text)

init_qp_name = "my-param"
init_qp_value = "some-value"

client = Client.open(STAC_URLS['PLANETARY-COMPUTER'],
parameters={init_qp_name: init_qp_value})

# Get the /collections endpoint
collections_link = client.get_single_link("data")
collection_href = collections_link.href + "/" + pc_collection_id

# Mock the request
requests_mock.get(collection_href, status_code=200, json=pc_collection_dict)

# Make the collections request
collection = client.get_collection(pc_collection_id)

# Mock the items endpoint
items_link = collection.get_single_link('items')
assert items_link is not None
requests_mock.get(items_link.href,
status_code=200,
json={
"type": "FeatureCollection",
"stac_version": "1.0.0",
"features": [],
"links": []
})

# Make the items request
_ = list(collection.get_items())

history = requests_mock.request_history
assert len(history) == 3

actual_qs = urlsplit(history[2].url).query
actual_qp = parse_qs(actual_qs)

# Check that the query param from the root Client is present
assert init_qp_name in actual_qp
assert len(actual_qp[init_qp_name]) == 1
assert actual_qp[init_qp_name][0] == init_qp_value

def test_get_collections_without_conformance(self, requests_mock):
"""Checks that the "data" endpoint is used if the API published the collections conformance class."""
pc_root_dict = read_data_file("planetary-computer-root.json", parse_json=True)
Expand Down