Usage#
Overview#
fews-py-wrapper provides a small typed wrapper around the Delft-FEWS
WebServices API. The main entry point is FewsWebServiceClient.
Contents#
Basic example#
from datetime import datetime, timezone
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
parameters = client.get_parameters()
locations = client.get_locations()
timeseries_datasets = client.get_timeseries(
location_ids=["Amanzimtoti_River_level"],
parameter_ids=["H.obs"],
start_time=datetime(2025, 3, 14, 10, 0, tzinfo=timezone.utc),
end_time=datetime(2025, 3, 15, 0, 0, tzinfo=timezone.utc),
)
first_dataset = timeseries_datasets[0]
Get parameters#
Use get_parameters() when you need the available FEWS parameter metadata before
requesting observations or forecasts.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
parameters = client.get_parameters()
for parameter in parameters[:3]:
print(parameter.id, parameter.name, parameter.unit)
Get locations#
Use get_locations() when you need the available FEWS locations before requesting
time series for a specific site.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
locations = client.get_locations()
for location in locations[:3]:
print(location.location_id, location.description, location.lat, location.lon)
Get time series#
get_timeseries() requests PI_NETCDF by default when document_format is
omitted. The FEWS response is retrieved as a ZIP file containing one or more
NetCDF files. The wrapper returns these as a list[xarray.Dataset], preserving
the original NetCDF layout of each member and the ZIP member order.
from datetime import datetime, timezone
import xarray as xr
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
datasets = client.get_timeseries(
location_ids=["Amanzimtoti_River_level"],
parameter_ids=["H.obs"],
start_time=datetime(2025, 3, 14, 10, 0, tzinfo=timezone.utc),
end_time=datetime(2025, 3, 15, 0, 0, tzinfo=timezone.utc),
)
first_dataset = datasets[0]
print(first_dataset)
merged = xr.merge(datasets, combine_attrs="override")
print(merged)
raw_timeseries = client.get_timeseries(
location_ids=["Amanzimtoti_River_level"],
parameter_ids=["H.obs"],
start_time=datetime(2025, 3, 14, 10, 0, tzinfo=timezone.utc),
end_time=datetime(2025, 3, 15, 0, 0, tzinfo=timezone.utc),
document_format="PI_JSON",
)
By default,
get_timeseries()requestsPI_NETCDFand returns alist[xarray.Dataset], preserving the original NetCDF member layout returned by FEWS.When
document_format="PI_JSON",get_timeseries()always returns the raw PI JSON dictionary. If you need xarray objects, requestdocument_format="PI_NETCDF"instead.
Post time series#
Use post_timeseries() to write PI time series data back to FEWS. The wrapper
accepts PI XML content and/or PI JSON content. The FEWS response is returned as
PI diagnostic XML.
from pathlib import Path
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
xml_payload = Path("tests/test_data/post_timeseries.xml").read_text(encoding="utf-8")
diag_xml = client.post_timeseries(
pi_time_series_xml_content=xml_payload,
filter_id="MEAS",
)
print(diag_xml)
The repository includes small reusable sample payloads in
tests/test_data/post_timeseries.xml and tests/test_data/post_timeseries.json.
Get filters#
Use get_filters() to retrieve the available FEWS filters. Optionally pass a
filter_id to retrieve only the subfilters of that filter.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
filters = client.get_filters()
for filter_ in filters:
print(filter_.id, filter_.name)
subfilters = client.get_filters(filter_id="MEAS")
for child in subfilters[0].children:
print(child.id, child.name)
Get workflows#
Use get_workflows() to retrieve the FEWS workflows exposed by the
/workflows endpoint. By default the wrapper requests PI_JSON, but you can
also request the raw PI_XML response.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
workflows = client.get_workflows()
for workflow in workflows[:3]:
print(workflow.id, workflow.name)
workflows_xml = client.get_workflows(document_format="PI_XML")
print(workflows_xml)
Post run task#
Use post_runtask() to trigger a one-off FEWS workflow execution through
POST /runtask. The wrapper returns the plain-text FEWS task ID, which you can
use with other FEWS task-run endpoints.
from datetime import datetime, timezone
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
task_id = client.post_runtask(
workflow_id="ImportObscape",
start_time=datetime(2025, 3, 18, 15, 0, tzinfo=timezone.utc),
end_time=datetime(2025, 3, 18, 16, 0, tzinfo=timezone.utc),
description="Run ImportObscape once from the wrapper",
run_option="all",
)
print(task_id)
When needed, you can also provide pi_parameters_xml_content with PI model
parameters XML content encoded as text.
Get task runs#
Use get_taskruns() to inspect FEWS task runs for a specific workflow. By
default the wrapper requests PI_JSON and returns a list of typed task-run
descriptors. You can also request the raw PI_XML response.
FEWS itself returns only forecast task runs by default. This means that a
non-forecast workflow can legitimately return an empty list unless you pass
only_forecasts=False.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
taskruns = client.get_taskruns(
workflow_id="ImportObscape",
task_run_count=10,
)
for task_run in taskruns:
print(task_run.id, task_run.status, task_run.dispatch_time)
non_forecast_taskruns = client.get_taskruns(
workflow_id="ftpClientConfig",
only_forecasts=False,
task_run_count=10,
)
print(non_forecast_taskruns)
taskruns_xml = client.get_taskruns(
workflow_id="ImportObscape",
document_format="PI_XML",
)
print(taskruns_xml)
If you call post_runtask() and then immediately query get_taskruns() for a
non-forecast workflow without setting only_forecasts=False, an empty typed
response does not necessarily indicate an error in the wrapper or in FEWS.
Get task run status#
Use get_taskrunstatus() to inspect the current status of a task ID returned by
post_runtask(). The current FEWS OpenAPI specification exposes PI_JSON for
this endpoint, and the wrapper returns a typed PiTaskRunStatusResponse.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
task_id = client.post_runtask(workflow_id="ImportObscape")
status = client.get_taskrunstatus(
task_id=task_id,
max_wait_millis=1000,
)
print(status.code, status.description, status.task_run_id)
Possible FEWS status codes are:
I: invalidP:pendingT: terminatedR: runningF: failedC: completed fully successfulD: completed partly successfulA: approvedB: approved partly successful.
Get what-if templates#
Use get_whatiftemplates() to inspect the available FEWS what-if templates and
their configurable properties. The current FEWS OpenAPI specification exposes
PI_JSON for this endpoint, and the wrapper returns a list of typed template
descriptors.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
templates = client.get_whatiftemplates()
for template in templates:
print(template.id, template.name)
specific_template = client.get_whatiftemplates(
what_if_template_id=templates[0].id,
)
print(specific_template[0].properties)
Each returned template can include one or more properties such as numbers, integers, strings, or date-time values, together with metadata like default, minimum, and maximum values.
Get what-if scenarios#
Use get_whatifscenarios() to inspect existing FEWS what-if scenarios. You can
optionally filter by template ID, scenario ID, or workflow ID. The current FEWS
OpenAPI specification exposes PI_JSON for this endpoint, and the wrapper
returns a list of typed scenario descriptors.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
scenarios = client.get_whatifscenarios()
for scenario in scenarios:
print(scenario.id, scenario.name, scenario.what_if_template_id)
specific_scenario = client.get_whatifscenarios(
what_if_scenario_id=scenarios[0].id,
)
print(specific_scenario[0].properties)
Post what-if scenarios#
Use post_whatifscenarios() to create a FEWS what-if scenario from a what-if
template. The current FEWS OpenAPI specification exposes PI_JSON for this
endpoint, and the wrapper returns a typed PiWhatIfScenarioDescriptor.
from fews_py_wrapper import FewsWebServiceClient
client = FewsWebServiceClient(base_url="https://example.com/FewsWebServices/rest")
templates = client.get_whatiftemplates()
template_id = templates[0].id
scenario = client.post_whatifscenarios(
what_if_template_id=template_id,
name="Wrapper what-if scenario",
single_run_what_if=False,
)
print(scenario.id, scenario.name, scenario.what_if_template_id)
At the moment, the generated FEWS OpenAPI client exposes only the query
parameters what_if_template_id, single_run_what_if, name,
document_format, and document_version for POST /whatifscenarios. It does
not expose a request body for setting scenario properties, so this wrapper
currently forwards only those parameters.
Create and run a what-if scenario end-to-end#
The what-if APIs can be combined with the workflow and task-run APIs in one script to:
retrieve the available what-if templates,
create a new what-if scenario from a template,
retrieve the created scenario,
find a workflow linked to the same what-if template, and
run that workflow with the scenario ID.
The example below uses the scenario ID returned by post_whatifscenarios()
instead of a hard-coded ID. It also uses unique names and descriptions so that
repeated runs are easier to distinguish in FEWS.
import os
from uuid import uuid4
from fews_py_wrapper import FewsWebServiceClient
base_url = os.getenv("FEWS_API_URL")
if not base_url:
raise RuntimeError("Set FEWS_API_URL before running this example.")
fews_client = FewsWebServiceClient(base_url=base_url, verify_ssl=False)
# Step 1: retrieve the available what-if templates.
templates = fews_client.get_whatiftemplates()
if not templates:
raise RuntimeError("No what-if templates were returned by FEWS.")
template = templates[0]
print("Selected what-if template:", template.id, template.name)
print()
# Step 2: create a new what-if scenario from the selected template.
scenario_name = f"wrapper-demo-{uuid4().hex[:8]}"
created_scenario = fews_client.post_whatifscenarios(
what_if_template_id=template.id,
name=scenario_name,
)
print("Created what-if scenario:")
print(created_scenario)
print()
# Step 3: retrieve the created scenario by the ID returned by FEWS.
scenarios = fews_client.get_whatifscenarios(
what_if_scenario_id=created_scenario.id,
)
if not scenarios:
raise RuntimeError(
f"The created scenario {created_scenario.id!r} was not returned by FEWS."
)
scenario = scenarios[0]
print("Retrieved what-if scenario:")
print(scenario)
print()
# Step 4: find a workflow linked to the scenario's what-if template.
scenario_template_id = scenario.what_if_template_id or template.id
workflows = fews_client.get_workflows()
workflow = next(
(
workflow
for workflow in workflows
if workflow.what_if_template_id == scenario_template_id
),
None,
)
if workflow is None:
raise RuntimeError(
"No workflow was returned with whatIfTemplateId "
f"{scenario_template_id!r}."
)
print("Selected workflow for the scenario:", workflow.id, workflow.name)
print()
# Step 5: run the workflow with the scenario ID.
task_description = f"fews-py-wrapper what-if demo {uuid4()}"
task_id = fews_client.post_runtask(
workflow_id=workflow.id,
scenario_id=scenario.id,
description=task_description,
)
print("Posted what-if task run.")
print("Returned task ID:", task_id)
print()
# Optional: ask FEWS for the current task-run status.
status = fews_client.get_taskrunstatus(task_id=task_id, max_wait_millis=1000)
print("Task run status:", status.code, f"({status.description})")
print("taskRunId returned by FEWS:", status.task_run_id)
Notes:
A workflow can be linked to a what-if template through its
workflow.what_if_template_idfield, which maps to FEWSwhatIfTemplateId.post_whatifscenarios()returns the created scenario descriptor. Use itsidwhen callingget_whatifscenarios()or when passingscenario_idtopost_runtask().The current generated FEWS OpenAPI client does not expose a request body for setting what-if scenario property values. The example therefore creates a scenario from the template defaults.
Run and track a workflow end-to-end#
The workflow and task-run APIs can be combined in one script to:
retrieve the available workflows,
choose a workflow,
post a new task run,
look up the corresponding task run entry, and
inspect its current status.
The example below uses a unique description so the newly created task run can be
found reliably in the get_taskruns() response. It also uses
only_forecasts=False because some workflows are not forecast workflows, and in
that case FEWS may otherwise return task_runs=[] by default.
import os
import time
from uuid import uuid4
from fews_py_wrapper import FewsWebServiceClient
base_url = os.getenv("FEWS_API_URL")
if not base_url:
raise RuntimeError("Set FEWS_API_URL before running this example.")
fews_client = FewsWebServiceClient(base_url=base_url, verify_ssl=False)
# Step 1: retrieve workflows
workflows = fews_client.get_workflows()
if not workflows:
raise RuntimeError("No workflows were returned by FEWS.")
# Prefer a known workflow when available; otherwise fall back to the first one.
preferred_workflow_ids = ["ImportObscape"]
workflow = next(
(
workflow
for workflow in workflows
if workflow.id in preferred_workflow_ids
),
workflows[0],
)
print("Selected workflow:", workflow)
print()
# Step 2: post a new task run with a unique description.
task_description = f"fews-py-wrapper demo task run {uuid4()}"
task_id = fews_client.post_runtask(
workflow_id=workflow.id,
description=task_description,
)
print("Task run posted.")
print("Returned task ID:", task_id)
print()
# Step 3: poll task runs until the newly created task becomes visible.
matched_taskrun = None
taskruns = fews_client.get_taskruns(
workflow_id=workflow.id,
only_forecasts=False,
)
matched_taskrun = next(
(
taskrun
for taskrun in taskruns
if taskrun.workflow_id == workflow.id
and taskrun.description == task_description
),
None,
)
if matched_taskrun is None:
print("The new task run is not visible yet in get_taskruns().")
else:
print("Matching task run entry returned by get_taskruns():")
print(matched_taskrun)
print()
# Step 4: query the current task-run status.
status = fews_client.get_taskrunstatus(task_id=task_id, max_wait_millis=1000)
print("Task run status:", status.code, f"({status.description})")
print("taskRunId returned by FEWS:", status.task_run_id)
Notes:
post_runtask()returns the FEWS task identifier immediately, but the task run may appear inget_taskruns()slightly later.get_taskruns()may needonly_forecasts=Falsewhen you query a non-forecast workflow.get_taskrunstatus()currently uses thePI_JSONresponse defined by the FEWS OpenAPI specification.