Getting started#

Installation#

sgu-client requires Python 3.11 or later.

uv add sgu-client
# or: pip install sgu-client

To convert responses to pandas DataFrames and Series, install the recommended extra:

uv add "sgu-client[recommended]"
# or: pip install "sgu-client[recommended]"

Creating a client#

SGUClient is the single entry point. Endpoints are grouped by domain on its attributes:

Attribute

Data

client.levels.observed

Measured groundwater levels from monitoring stations

client.levels.modeled

Modeled groundwater levels from SGU-HYPE

client.chemistry

Groundwater chemistry sampling sites and analysis results

Use the client as a context manager. It reuses one HTTP session for all requests and closes it when the with block ends:

from sgu_client import SGUClient

with SGUClient() as client:
    station = client.levels.observed.get_station_by_name(station_id="95_2")
    print(station.properties.station_name)

In a notebook or a long-running application, where one with block is impractical, create the client once, reuse it, and call close() when you are done.

The examples further down this page assume such a client.

Typed responses#

Every method returns a typed Pydantic model, so your editor can autocomplete fields. Collections follow the GeoJSON structure of the underlying OGC API: a features list where each feature has a geometry and properties.

stations = client.levels.observed.get_stations(limit=5)

for station in stations.features:
    print(station.properties.station_name, station.properties.municipality)
    print(station.geometry.coordinates)

Fields have English names, and each maps to the Swedish name used by the SGU API (for example station_id ↔ platsbeteckning). The API Reference lists the API name for every field.

Dates and times#

Every date and time is a timezone-aware datetime in UTC, as the SGU API labels it (its values end in Z):

  • Timestamps keep their time: observation_date "2024-12-31T04:00:00Z" becomes datetime(2024, 12, 31, 4, 0, tzinfo=UTC).

  • Date-only fields arrive as "1996-04-29Z" and become midnight UTC, so .date() gives the calendar day.

  • Missing values are None. A value that can’t be parsed, or that has no timezone, is also None, and a warning is logged naming the field and the value.

SGU doesn’t document which timezone its data uses, so the client takes the Z at its word. One case to keep in mind: logger readings in observation_date fit UTC, but they could also be Swedish standard time (UTC+1) labelled as UTC.

to_dict() returns datetime objects. For JSON, use model_dump_json(), which writes ISO 8601 strings.

Configuration#

Pass an SGUConfig to change timeouts, retries or logging:

from sgu_client import SGUClient, SGUConfig

config = SGUConfig(timeout=60, max_retries=5, log_level="DEBUG")

with SGUClient(config=config) as client:
    ...

See SGUConfig for all options and their defaults.

Handling errors#

All errors raised by the client inherit from SGUClientError:

from sgu_client import SGUAPIError, SGUClientError, SGUTimeoutError

try:
    measurements = client.levels.observed.get_measurements_by_name(station_id="95_2")
except SGUTimeoutError:
    ...  # the request took longer than `timeout`
except SGUAPIError as err:
    print(err.status_code, err.response_data)
except SGUClientError:
    ...  # any other client error

Convenience lookups such as get_station_by_name() raise SGUNotFoundError when nothing matches and SGUAmbiguousError when more than one feature matches. Both are also ValueErrors, as are the errors for invalid arguments.