Skip to content
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
9 changes: 9 additions & 0 deletions pyvolca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ git cliff --unreleased --tag pyvolca-v0.X.Y # render as a released section

Then paste the rendered block at the top of this file and tighten wording.

## [0.7.2] - 2026-07-08

### Added

- `Client.search_activities` gains a `classification_match` argument
(`MatchMode.CONTAINS` default, or `MatchMode.EXACT`). A classification filter
can now require exact equality instead of substring — the match mode the
engine and MCP already accept, finally reachable from the typed client.

## [0.7.1] - 2026-07-07

### Fixed
Expand Down
8 changes: 6 additions & 2 deletions pyvolca/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pyvolca speaks one revision of the engine's JSON wire format; the engine adverti

_Generated from `volca._compat` — run `python scripts/gen_api_md.py` to regenerate._

This build of **pyvolca 0.7.1** speaks wire format **1** and requires a VoLCA engine **≥ v0.8.0**.
This build of **pyvolca 0.7.2** speaks wire format **1** and requires a VoLCA engine **≥ v0.8.0**.

<!-- END: compatibility -->

Expand Down Expand Up @@ -672,7 +672,7 @@ Remove ``dep_name`` from the target database's dependencies.

Returns the updated ``DatabaseSetupInfo`` dict.

##### `Client.search_activities(name: str | None = None, *, geo: str | None = None, product: str | None = None, preset: str | None = None, classification: str | None = None, classification_value: str | None = None, page: int | None = None, page_size: int | None = None, limit: int | None = None, offset: int | None = None, exact: bool = False) -> SearchResults[Activity]`
##### `Client.search_activities(name: str | None = None, *, geo: str | None = None, product: str | None = None, preset: str | None = None, classification: str | None = None, classification_value: str | None = None, classification_match: MatchModeLike | None = None, page: int | None = None, page_size: int | None = None, limit: int | None = None, offset: int | None = None, exact: bool = False) -> SearchResults[Activity]`

Search activities in the current database.

Expand All @@ -691,6 +691,10 @@ Args:
preset: Apply a named classification preset configured in the engine.
classification: System name (``"ISIC rev.4 ecoinvent"``).
classification_value: Substring within that system's value.
classification_match: How ``classification_value`` is compared —
:class:`MatchMode.CONTAINS` (default, substring) or
:class:`MatchMode.EXACT` (case-insensitive equality). Ignored
when ``classification`` is unset.
page: 1-based page number. Must be paired with ``page_size`` —
offset cannot be derived from page alone.
page_size: Items per page (becomes the wire-level ``limit``).
Expand Down
2 changes: 1 addition & 1 deletion pyvolca/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pyvolca"
version = "0.7.1"
version = "0.7.2"
description = "Python client for VoLCA — Life Cycle Assessment engine"
requires-python = ">=3.10"
license = "Apache-2.0"
Expand Down
11 changes: 11 additions & 0 deletions pyvolca/src/volca/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
LCIABatchResult,
LCIAResult,
MatchMode,
MatchModeLike,
Method,
PathResult,
Preset,
Expand Down Expand Up @@ -849,6 +850,7 @@ def search_activities(
preset: str | None = None,
classification: str | None = None,
classification_value: str | None = None,
classification_match: MatchModeLike | None = None,
page: int | None = None,
page_size: int | None = None,
limit: int | None = None,
Expand All @@ -872,6 +874,10 @@ def search_activities(
preset: Apply a named classification preset configured in the engine.
classification: System name (``"ISIC rev.4 ecoinvent"``).
classification_value: Substring within that system's value.
classification_match: How ``classification_value`` is compared —
:class:`MatchMode.CONTAINS` (default, substring) or
:class:`MatchMode.EXACT` (case-insensitive equality). Ignored
when ``classification`` is unset.
page: 1-based page number. Must be paired with ``page_size`` —
offset cannot be derived from page alone.
page_size: Items per page (becomes the wire-level ``limit``).
Expand All @@ -891,6 +897,11 @@ def search_activities(
preset=preset,
classification=classification,
classification_value=classification_value,
classification_mode=(
MatchMode(classification_match).value
if classification_match is not None
else None
),
exact=exact,
)

Expand Down
1 change: 1 addition & 0 deletions pyvolca/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def fixture_spec() -> dict[str, Any]:
{"name": "preset", "in": "query", "required": False, "schema": {"type": "string"}},
{"name": "classification", "in": "query", "required": False, "schema": {"type": "string"}},
{"name": "classification-value", "in": "query", "required": False, "schema": {"type": "string"}},
{"name": "classification-mode", "in": "query", "required": False, "schema": {"type": "string"}},
{"name": "limit", "in": "query", "required": False, "schema": {"type": "integer"}},
{"name": "offset", "in": "query", "required": False, "schema": {"type": "integer"}},
],
Expand Down
23 changes: 23 additions & 0 deletions pyvolca/tests/test_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,29 @@ def test_search_activities_drops_none_kwargs(self, mocked_client, make_response,
assert "geo" not in params
assert "limit" not in params

def test_search_activities_classification_match_sends_mode(self, mocked_client, make_response, empty_envelope):
"""classification_match reaches the wire as ``classification-mode``."""
client, session = mocked_client
session.get.return_value = make_response(empty_envelope())
client.search_activities(classification="Category", classification_value="Food", classification_match="exact")
params = dict(session.get.call_args[1]["params"])
assert params["classification-value"] == "Food"
assert params["classification-mode"] == "exact"

def test_search_activities_omits_mode_by_default(self, mocked_client, make_response, empty_envelope):
"""No classification_match → no ``classification-mode`` query param (server default: contains)."""
client, session = mocked_client
session.get.return_value = make_response(empty_envelope())
client.search_activities(classification="Category", classification_value="Food")
params = dict(session.get.call_args[1]["params"])
assert "classification-mode" not in params

def test_search_activities_rejects_bad_match_mode(self, mocked_client):
"""A typo'd match mode fails client-side before any HTTP call."""
client, _ = mocked_client
with pytest.raises(ValueError):
client.search_activities(classification="Category", classification_value="Food", classification_match="exct")

def test_search_activities_page_kwargs_compute_offset(self, mocked_client, make_response, empty_envelope):
"""page=3 + page_size=20 must translate to offset=40, limit=20."""
client, session = mocked_client
Expand Down
Loading