API Reference¶
Variable registry additions¶
ERA5 pressure levels. reanalysis-era5-pressure-levels (catalog short
name era5.pressure_levels) now describes the CDS pressure-level fields
u, v, w (omega, Pa s⁻¹), t, z (geopotential) and q, each with a
cds alias so
CDSSource(...).download("reanalysis-era5-pressure-levels", Path("uv.nc"), variables=["u", "v"], levels=PressureLevels([1000, 925]))
emits the CDS form names without hand-typing them (era5.pressure_levels
is the catalog short name for that dataset id — see
describe("era5.pressure_levels")). The single-level preset gains blh
(boundary-layer height). The wrf aliases on u, v and blh name the
raw wrfout variables that map onto the same CF quantity; q carries no
wrf alias because QVAPOR is a mixing ratio w.r.t. dry air and must be
converted (q = r / (1 + r)) before it is CF specific humidity.
Methane / trace gas. xch4, xch4_bias_corrected, xch4_precision,
column_averaging_kernel, ch4_profile_apriori, dry_air_subcolumns and
qa_value carry the TROPOMI L2 product names as tropomi aliases;
ch4_enhancement (ppm m) is the EMIT L2B plume-enhancement quantity, and
ch4_column (kg m⁻², atmosphere_mass_content_of_methane) is the canonical
column-mass output of the xrops.atm.gas.ch4 operators. All of them
resolve through xrproducts.resolve and stamp CF attributes through
apply_cf_attrs.
Local L2 products¶
LocalL2Source is the DataSource for satellite L2 product files that are
already on disk — nothing is fetched (download() raises
NotImplementedError). The catalog short names tropomi.ch4, emit.ch4 and
ghgsat.ch4 (source="local") each map to a product opener that turns one
file into a flat CF Dataset. The openers read NetCDF, and the core install
ships no backend for it — install the local extra
(pip install 'xrtoolz-products[local]', which adds netcdf4):
| Short name | Opener | Kind | Layout |
|---|---|---|---|
tropomi.ch4 |
open_tropomi_ch4_l2(path, qa_min=0.5, keep_groups=False) |
DatasetKind.SWATH |
(time, scanline, ground_pixel[, layer, level, corner]) |
emit.ch4 |
open_emit_ch4_l2b(path, glt_path=None) |
DatasetKind.SCENE |
(time, y, x) |
ghgsat.ch4 |
open_ghgsat_ch4_l2(path) |
DatasetKind.SCENE |
(time, y, x) |
TROPOMI. The four product groups (PRODUCT and
PRODUCT/SUPPORT_DATA/{DETAILED_RESULTS,INPUT_DATA,GEOLOCATIONS}) are read
with xarray.open_datatree and merged on the shared swath dims. Variables are
renamed through the registry's tropomi aliases (methane_mixing_ratio →
xch4, methane_mixing_ratio_bias_corrected → xch4_bias_corrected,
methane_mixing_ratio_precision → xch4_precision,
methane_profile_apriori → ch4_profile_apriori, surface_pressure → sp;
column_averaging_kernel, dry_air_subcolumns and qa_value keep their
names, pressure_interval has no registry entry and is passed through), and
latitude / longitude become the 2-D lat / lon coordinates. Time keeps
the product's shape: the length-1 time index is the granule reference time
(PRODUCT/time, seconds since 2010-01-01) and the per-scanline delta_time
offset is folded into a scanline_time (time, scanline) datetime64
coordinate. qa_value < qa_min pixels are set to NaN (subset_where, shape
preserved); keep_groups=True keeps the raw product names.
EMIT. The L2B enhancement raster (methane_plume_complex as distributed,
or the registry alias ch4_enhancement — either becomes ch4_enhancement in
ppm m — plus ch4_uncertainty when present) is read from a NetCDF container;
rioxarray is not an xrproducts dependency, so GeoTIFF distributions have to
be converted first. With glt_path the scene is orthorectified through the
geometric lookup table (glt_x / glt_y, 1-based, 0 = no data) and the
2-D lon / lat of the orthorectified grid come from the GLT's own lon /
lat or its GDAL geotransform attribute (full six-coefficient affine). A
scalar time variable on the raster is carried through as the scene
timestamp. Several scenes take one lookup table each via
glt_paths=[...], zipped with paths=[...].
GHGSat. A per-plume NetCDF with xch4 (ppb) and/or ch4_enhancement on
(y, x) and 2-D latitude / longitude, promoted to lon / lat coords.
LocalL2Source.open(dataset_id, path=..., bbox=..., time=..., qa_min=...,
variables=...) applies the screening after the opener: bbox through
subset_bbox on the 2-D coordinates (where(..., drop=True)) after the box
is normalised to the dataset's longitude convention ([-180, 180] when any
lon is negative, [0, 360] when any exceeds 180 — a box that still crosses
the antimeridian afterwards raises), time as a per-scanline mask on
scanline_time for swaths or a mask on the time coordinate for scenes, and
paths=[a, b] concatenates granules along time in the order given.
subset_bbox, subset_where and subset_time now live in
xrproducts.types (pure xarray) and are re-exported unchanged by
xrops.geom, so the reader can screen without importing xrtoolz. See the
TROPOMI recipe for a worked example.
Emission inventories. open_ch4_inventory(path, variable=None, units=None,
source=None) opens a gridded CH₄ inventory (EDGAR, GFEI, EPA) as
ch4_emission_flux in kg m⁻² s⁻¹ on ([time,] lat, lon), with lat
ascending and lon wrapped to [-180, 180). The file's area-flux units are
converted through UNIT_FACTORS (kg m⁻² s⁻¹, g m⁻² s⁻¹, Mg km⁻² a⁻¹ with a
365.25-day year); the factor and the original units are kept as attributes.
Per-cell totals such as EDGAR's emi files (tonnes per cell, per year or per
month) are rejected because the period is not in the file: read the flx
files instead. It is a plain opener, not a catalog entry.
xrproducts
¶
xrproducts — authenticated archive readers for xarray geodata.
xrproducts turns a typed :class:Request into a service-specific payload
and downloads or opens an :class:xarray.Dataset from external geoscience
archives (CMEMS, CDS, AEMET). The typed request vocabulary
(:class:BBox, :class:TimeRange, :class:Variable, ...) lives in
:mod:xrproducts.types; this top-level namespace re-exports the reader API
and the common request types flat.
Reader API:
- :class:
DataSource, :class:DatasetInfo, :class:DatasetKind - Adapters: :class:
CMEMSSource, :class:CDSSource, :class:AemetSource, :class:LocalL2Source(files already on disk; TROPOMI / EMIT / GHGSat methane L2 openers :func:open_tropomi_ch4_l2, :func:open_emit_ch4_l2b, :func:open_ghgsat_ch4_l2; gridded emission inventories through :func:open_ch4_inventory). - Credentials: :class:
CMEMSCredentials, :class:CDSCredentials, :class:AEMETCredentials, :func:load_cmems, :func:load_cds, :func:load_aemet. - Catalog: :data:
CATALOG, :class:CatalogEntry, :func:all_entries, :func:describe. - AEMET extras: :class:
AemetArchive, :class:AemetError, :class:AemetAuthError, :class:AemetRateLimitError, :class:ArchiveCoverage. - CDS extras: :class:
CDSInsituArchive, :class:CDSFormProfile, :data:INSITU, :data:REANALYSIS.
Request vocabulary (also available via :mod:xrproducts.types):
- Spatial / temporal / vertical: :class:
BBox, :class:TimeRange, :class:DepthRange, :class:PressureLevels. - Composition: :class:
Request. - Stations: :class:
Station, :class:StationCollection. - Variables: :class:
Variable, :data:REGISTRY, :func:resolve, :func:register. - Validation: :func:
validate_variable, :func:validate_dataset, :func:apply_cf_attrs, :class:ValidationReport, :class:Issue, :class:Severity. - Subsetting: :func:
subset_bbox, :func:subset_where, :func:subset_time.
REANALYSIS
module-attribute
¶
REANALYSIS = CDSFormProfile(family='reanalysis', format_key='format', format_default='netcdf', includes_product_type=True, uses_pressure_level=False, uses_time=True)
Profile for surface / single-level ERA5 reanalyses (and the fallback).
format=netcdf, product_type=reanalysis (source-configurable),
area, year/month/day (all arrays), hourly time. Surface and
land products reject pressure_level, so it is not emitted here —
use :data:REANALYSIS_PRESSURE for pressure-level datasets.
AemetArchive
¶
AemetArchive(root: Path, source: AemetSource)
Append-only local mirror of AEMET station observations.
Everything (inventory + per-preset time series) lives under
root as GeoParquet. Dense fields like future gridded AEMET
products are out of scope for this class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
Directory that will hold one file per preset. |
required |
source
|
AemetSource
|
:class: |
required |
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
sync_stations
¶
sync_stations() -> StationCollection
Refresh the cached station inventory and return it.
load_stations
¶
load_stations() -> StationCollection
Return the inventory as a :class:StationCollection.
Refreshes from AEMET if no cached copy exists.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
load_stations_geodataframe
¶
Return the inventory as a :class:geopandas.GeoDataFrame.
Geometry is EPSG:4326 points. Refreshes from AEMET if no cached copy exists.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
sync
¶
sync(preset: str, *, stations: StationCollection | None = None, since: str | None = None, until: str | None = None) -> Dataset
Backfill or update a preset. Returns the freshly-fetched slice.
sync returns the in-memory xr.Dataset that was just
fetched (useful for immediate inspection). The on-disk archive
is written as a long-format GeoParquet file; call :meth:load
to read it back as a :class:geopandas.GeoDataFrame.
Behaviour:
sincedefaults to one day after the archive's global latest stored time (the newest row across all stations), or the preset-specific floor (e.g. 1920 for daily / monthly) when the archive is empty. Per-station resume — so stations missing recent records get backfilled even when others are up-to-date — is not implemented; use an explicitsinceif you need that behaviour.untildefaults tonowin UTC.- Re-running with the same window overwrites the overlapping slice; it is idempotent, not additive.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
load
¶
Open the archive for preset as a :class:geopandas.GeoDataFrame.
The GeoDataFrame is long-format: one row per
(station, time) sample, with the geometry column
carrying each station's EPSG:4326 point.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
load_dataset
¶
Open the archive for preset as an (station, time) xarray dataset.
Convenience for workflows that prefer the cube view over the long-format GeoDataFrame.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
coverage
¶
coverage(preset: str) -> list[ArchiveCoverage]
Return per-station first / last / gap statistics for preset.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/archive.py
AemetAuthError
¶
Bases: AemetError
Missing or rejected API key.
AemetError
¶
Bases: RuntimeError
Any AEMET-level failure (auth, rate limit, bad envelope, 5xx).
AemetRateLimitError
¶
Bases: AemetError
Quota exhausted even after backoff.
AemetSource
¶
AemetSource(credentials: AEMETCredentials | None = None, client: Any | None = None, base_url: str = AEMET_BASE_URL, timeout_s: float = DEFAULT_TIMEOUT_S, max_retries: int = DEFAULT_MAX_RETRIES, max_workers: int = DEFAULT_MAX_WORKERS, min_interval_s: float = 0.0)
Bases: DataSource
Adapter for AEMET OpenData observation endpoints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credentials
|
AEMETCredentials | None
|
Explicit credentials. When |
None
|
client
|
Any | None
|
Optional pre-built HTTP client (must expose a
|
None
|
base_url
|
str
|
Override the API root (useful for staging / mocks). |
AEMET_BASE_URL
|
timeout_s
|
float
|
Per-request timeout. |
DEFAULT_TIMEOUT_S
|
max_retries
|
int
|
Retries for 429 / transient network errors. |
DEFAULT_MAX_RETRIES
|
max_workers
|
int
|
Thread pool size for parallel station fetches. |
DEFAULT_MAX_WORKERS
|
min_interval_s
|
float
|
Minimum seconds between consecutive HTTP calls,
enforced globally across the worker pool. AEMET's rate
limit is |
0.0
|
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
download
¶
download(dataset_id: str, output: Path, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Path
Download dataset_id to a NetCDF file at output.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
open
¶
open(dataset_id: str, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, stations: Iterable[str] | StationCollection | None = None, **extras: Any) -> Dataset
Open dataset_id as an (station, time) xarray Dataset.
dataset_id is one of the preset IDs from
:mod:~xrproducts._src.aemet.catalog (aemet_daily,
aemet_hourly, aemet_monthly, aemet_pollution,
aemet_stations, aemet_normals, aemet_extremes).
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
list_stations
¶
list_stations(bbox: BBox | None = None) -> StationCollection
Return the full AEMET station inventory, optionally filtered.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
get_daily
¶
get_daily(stations: Iterable[str], *, time: TimeRange | None = None, variables: list[str | Variable] | None = None) -> Dataset
Fetch daily climatology for stations in time.
Automatically chunks each station's window into 180-day slices (AEMET's undocumented per-request cap) and fetches in parallel.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
get_hourly
¶
get_hourly(stations: Iterable[str], *, time: TimeRange | None = None, variables: list[str | Variable] | None = None) -> Dataset
Fetch the latest ~24 h of hourly observations for stations.
The AEMET hourly endpoint is rolling — it returns whatever window
of recent observations the station has published (typically the
last 24 hours). There is no historical-window query, but time
(when given) trims the returned dataset to the requested window.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
get_monthly
¶
get_monthly(stations: Iterable[str], *, time: TimeRange | None = None, variables: list[str | Variable] | None = None) -> Dataset
Fetch monthly aggregates for stations over time.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 | |
get_normals
¶
get_normals(stations: Iterable[str], *, variables: list[str | Variable] | None = None) -> Dataset
Fetch 1981–2010 climate normals for stations.
Emitted as a (station, month) dataset with month=1..12.
variables selects a subset of the normals fields.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
get_extremes
¶
Fetch record extremes (P=precip, T=temp, V=wind) per station.
Extremes are a ragged, per-field set of records. We emit them as
a minimal (station,) dataset with the raw dictionaries kept
in attrs['records'] rather than guessing a schema that may
vary per station.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
get_pollution
¶
get_pollution(stations: Iterable[str], *, time: TimeRange | None = None, variables: list[str | Variable] | None = None) -> Dataset
Fetch EMEP background-pollution time series for stations.
AEMET returns irregular hourly/daily mixed samples — we pass
them through as (station, time) keeping whatever time axis
the payload reports. variables selects a subset of the
pollutant fields.
Source code in packages/xrtoolz-products/src/xrproducts/_src/aemet/source.py
ArchiveCoverage
dataclass
¶
ArchiveCoverage(preset: str, station_id: str, first: Timestamp | None, last: Timestamp | None, n_timesteps: int, gap_fraction: float)
Per-station span of what the archive already holds for a preset.
DatasetInfo
dataclass
¶
DatasetInfo(dataset_id: str, source: str, title: str, kind: DatasetKind = GRIDDED, variables: tuple[Variable, ...] = (), spatial_coverage: BBox | None = None, temporal_coverage: tuple[str, str] | None = None, doi: str | None = None, license: str | None = None, notes: str | None = None, extras: dict[str, Any] = dict(), form_profile: Any = None)
Static description of a remote dataset.
Adapters return these from :meth:DataSource.describe and the
catalog stores them alongside default request parameters.
DatasetKind
¶
Bases: StrEnum
Discriminator for how a dataset is laid out once materialized.
gridded— regular lon/lat (+time) cube, the common case.alongtrack— 1-D samples along a satellite ground track.profiles— collection of vertical profiles at scattered locations (Argo floats, CTD casts, gliders).trajectory— drifters / moving platforms.stations— fixed points, one time-series per station. Maps to CF'sfeatureType = "timeSeries"(CF 9.3) with dims(station, time)and per-station coords.swath— satellite L2 swath on(time, scanline, ground_pixel)with 2-D lon/lat coordinates (TROPOMI).scene— single L2 scene / plume raster on(y, x)with 2-D lon/lat coordinates (EMIT, GHGSat).
Downstream code (regridding, masking, plotting) branches on this; e.g. antimeridian BBox wrapping is meaningless for profiles.
DataSource
¶
Bases: ABC
Abstract interface implemented by every adapter.
source_id
instance-attribute
¶
Short slug used to look up per-source aliases in :class:Variable.
list_datasets
abstractmethod
¶
list_datasets() -> list[DatasetInfo]
describe
abstractmethod
¶
describe(dataset_id: str) -> DatasetInfo
download
abstractmethod
¶
download(dataset_id: str, output: Path, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Path
Download dataset_id to output and return the path.
Source code in packages/xrtoolz-products/src/xrproducts/_src/base.py
open
abstractmethod
¶
open(dataset_id: str, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Dataset
Open dataset_id lazily as an :class:xarray.Dataset.
Source code in packages/xrtoolz-products/src/xrproducts/_src/base.py
CatalogEntry
dataclass
¶
Short-name-to-source mapping with optional request defaults.
CDSFormProfile
dataclass
¶
CDSFormProfile(family: str, format_key: str = 'format', format_default: str = 'netcdf', fixed: Mapping[str, Any] = dict(), includes_product_type: bool = False, uses_area: bool = True, uses_pressure_level: bool = False, year_is_array: bool = True, uses_time: bool = False, required_extras: tuple[str, ...] = tuple())
Shape of a CDS retrieve() form for a dataset family.
Attributes:
| Name | Type | Description |
|---|---|---|
family |
str
|
Short slug identifying the family ( |
format_key |
str
|
Name of the form field that carries the output
format. |
format_default |
str
|
Default value for |
fixed |
Mapping[str, Any]
|
Form keys this family always sends with a fixed value
(e.g. |
includes_product_type |
bool
|
Whether |
uses_area |
bool
|
Whether |
uses_pressure_level |
bool
|
Whether :class: |
year_is_array |
bool
|
Whether |
uses_time |
bool
|
Whether the family expects an hourly |
required_extras |
tuple[str, ...]
|
Keys the caller must supply via |
CDSInsituArchive
¶
CDSInsituArchive(root: Path, preset: str, source: CDSSource | None = None, *, time_aggregation: str = 'daily', variables: tuple[Variable, ...] | None = None)
Append-only local mirror of a CDS in-situ preset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
Directory that will hold one subtree per preset. |
required |
preset
|
str
|
Either |
required |
source
|
CDSSource | None
|
:class: |
None
|
time_aggregation
|
str
|
One of |
'daily'
|
variables
|
tuple[Variable, ...] | None
|
Restrict fetches to these variables. |
None
|
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
sync
¶
sync(start: str | Timestamp, end: str | Timestamp, *, bbox: BBox | None = None, since: str | Timestamp | None = None, overwrite: bool = False) -> DataFrame
Fetch year-chunks spanning [start, end] and append to disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
str | Timestamp
|
Earliest year to fetch. Inclusive. |
required |
end
|
str | Timestamp
|
Latest year to fetch. Inclusive. |
required |
bbox
|
BBox | None
|
Optional server-side spatial filter. CDS in-situ
accepts an |
None
|
since
|
str | Timestamp | None
|
If given, skip years strictly earlier than this and force a re-fetch of years at/after the cutoff even if they're already in the manifest (targeted refresh). |
None
|
overwrite
|
bool
|
Re-download every year in the window regardless of the manifest. |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The concatenated freshly-fetched data (empty if everything |
DataFrame
|
was already cached). |
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | |
load
¶
Open the archive as a :class:geopandas.GeoDataFrame.
Optionally filter to [start, end] inclusive.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
load_dataset
¶
Open the archive as an (station, time) xarray Dataset.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
load_stations
¶
Distinct (station_id, lon, lat) inventory as a GeoDataFrame.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
coverage
¶
Per-station first/last/n_timesteps for the archive.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/archive.py
CDSSource
¶
CDSSource(credentials: CDSCredentials | None = None, client: Any | None = None, format: str | None = None, product_type: str | None = 'reanalysis')
Bases: DataSource
Adapter around the cdsapi Python client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credentials
|
CDSCredentials | None
|
Explicit :class: |
None
|
client
|
Any | None
|
Optional pre-built |
None
|
format
|
str | None
|
CDS output format. When |
None
|
product_type
|
str | None
|
Default |
'reanalysis'
|
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/source.py
download
¶
download(dataset_id: str, output: Path, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Path
Retrieve a dataset to output via cdsapi.Client.retrieve.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/source.py
open
¶
open(dataset_id: str, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Dataset
Download then open the resulting file with xarray.open_dataset.
CDS has no native lazy access path, so this always materialises
a local file (under the request cache) before handing back an
xr.Dataset. Both the cache file suffix and the xarray engine
follow :attr:format, so reconfiguring to "grib" remains
consistent end-to-end.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cds/source.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
CMEMSSource
¶
CMEMSSource(credentials: CMEMSCredentials | None = None, client: Any | None = None)
Bases: DataSource
Adapter around the copernicusmarine Python client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
credentials
|
CMEMSCredentials | None
|
Explicit :class: |
None
|
client
|
Any | None
|
Optional pre-built client ( |
None
|
Source code in packages/xrtoolz-products/src/xrproducts/_src/cmems/source.py
download
¶
download(dataset_id: str, output: Path, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Path
Download a subset to a NetCDF file at output.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cmems/source.py
open
¶
open(dataset_id: str, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Dataset
Open a lazy dataset via copernicusmarine.open_dataset.
Source code in packages/xrtoolz-products/src/xrproducts/_src/cmems/source.py
CDSCredentials
dataclass
¶
URL / key pair for the Climate Data Store.
CMEMSCredentials
dataclass
¶
Username / password pair for Copernicus Marine.
LocalL2Source
¶
Bases: DataSource
Open satellite L2 product files from the local filesystem.
dataset_id selects a product opener from :data:LOCAL_DATASETS
("tropomi.ch4", "emit.ch4", "ghgsat.ch4"); the file path
comes through path= (or paths= for multi-granule concatenation
along time). bbox is applied with
:func:xrproducts.subset_bbox on the 2-D lon/lat coordinates
(after matching the box to the dataset's longitude convention) and
time as a mask on the time coordinate (or on the per-scanline
scanline_time for swaths). Nothing is fetched: :meth:download
raises.
The openers read NetCDF, which the core install does not ship a
backend for: pip install 'xrtoolz-products[local]'.
download
¶
download(dataset_id: str, output: Path, *, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, **extras: Any) -> Path
Not supported: the product files are already on disk.
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Always. |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/source.py
open
¶
open(dataset_id: str, *, path: str | Path | None = None, paths: Sequence[str | Path] | None = None, glt_paths: Sequence[str | Path] | None = None, variables: list[str | Variable] | None = None, bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, qa_min: float | None = None, **extras: Any) -> Dataset
Open one or more product files as a single flat Dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset_id
|
str
|
Catalog short name ( |
required |
path
|
str | Path | None
|
The product file. Exactly one of |
None
|
paths
|
Sequence[str | Path] | None
|
Several granules / scenes, concatenated along |
None
|
glt_paths
|
Sequence[str | Path] | None
|
One EMIT geometric lookup table per entry of
|
None
|
variables
|
list[str | Variable] | None
|
Registry names or :class: |
None
|
bbox
|
BBox | None
|
Keep only pixels whose 2-D |
None
|
time
|
TimeRange | None
|
Keep measurements inside the window. Swaths are masked
on their per-scanline |
None
|
depth
|
DepthRange | None
|
Ignored (no vertical axis in these products). |
None
|
levels
|
PressureLevels | None
|
Ignored (no vertical axis in these products). |
None
|
qa_min
|
float | None
|
Forwarded to the opener when given (TROPOMI:
|
None
|
**extras
|
Any
|
Forwarded to the opener ( |
{}
|
Returns:
| Type | Description |
|---|---|
Dataset
|
The screened Dataset. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If neither or both of |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/source.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
BBox
dataclass
¶
Axis-aligned lon/lat bounding box.
Longitude is accepted in either [-180, 180] or [0, 360]
convention; use :meth:to_180 / :meth:to_360 to normalize. A
lon_min > lon_max box is interpreted as crossing the
antimeridian.
from_tuple
classmethod
¶
from_tuple(t: tuple[float, float, float, float]) -> BBox
Build from (lon_min, lon_max, lat_min, lat_max).
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
to_180
¶
to_180() -> BBox
Return a copy with longitudes normalized to [-180, 180].
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
to_360
¶
to_360() -> BBox
Return a copy with longitudes normalized to [0, 360].
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
as_cmems
¶
copernicusmarine.subset kwargs.
Raises:
| Type | Description |
|---|---|
ValueError
|
if the box crosses the antimeridian — passing
|
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
as_cds_area
¶
CDS area key: [North, West, South, East].
Raises:
| Type | Description |
|---|---|
ValueError
|
if the box crosses the antimeridian — |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
as_xarray_sel
¶
ds.sel(**bbox.as_xarray_sel()) selector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lon
|
str
|
Name of the longitude coordinate. |
'lon'
|
lat
|
str
|
Name of the latitude coordinate. |
'lat'
|
lat_descending
|
bool
|
Set when the dataset's latitude index runs
north-to-south (ERA5 and most CDS reanalysis output do).
|
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if the box crosses the antimeridian — a single
|
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/geometry.py
DepthRange
dataclass
¶
Continuous depth window (positive-down, metres).
Issue
dataclass
¶
Issue(variable: str, severity: Severity, code: str, message: str)
Single validation finding.
PressureLevels
dataclass
¶
Discrete pressure levels in hPa (CDS-style).
Request
dataclass
¶
Request(variables: tuple[str, ...], bbox: BBox | None = None, time: TimeRange | None = None, depth: DepthRange | None = None, levels: PressureLevels | None = None, extras: dict[str, Any] | None = None)
Canonical request payload consumed by data adapters.
Not every field applies to every source; adapters pick what they need. Keeping one struct means the catalog and the UI speak the same shape.
Severity
¶
Bases: StrEnum
Two-level severity for validation issues.
Station
dataclass
¶
Station(id: str, name: str, lon: float, lat: float, altitude: float | None = None, wmo_id: str | None = None, source: str | None = None, city: str | None = None, province: str | None = None, community: str | None = None, timezone: str | None = None, start_time: str | None = None, end_time: str | None = None, active: bool | None = None, attrs: Mapping[str, Any] = dict())
One fixed observation station.
Coordinates are decimal degrees in the [-180, 180] / [-90, 90]
convention. city / province / community reflect the
administrative hierarchy exposed by AEMET and most national services;
adapters that don't split this way can leave them None.
distance_to
¶
Great-circle distance to (lon, lat) in kilometres.
in_bbox
¶
in_bbox(bbox: BBox) -> bool
Whether the station falls inside bbox (inclusive).
Station longitudes are stored in [-180, 180]; bbox may be
supplied in either convention, so it is normalized to [-180, 180]
first (which may turn a [0, 360] box into an antimeridian-
crossing one, handled below).
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/station.py
StationCollection
dataclass
¶
StationCollection(stations: tuple[Station, ...] = ())
Immutable collection of :class:Station with set-like filters.
Supports iteration, len, containment, tuple-style indexing, and
conversion to :class:pandas.DataFrame. All filter methods return a
new collection rather than mutating in place, so chains like
stations.filter(province="Cantabria").within(bbox) are safe.
from_iter
classmethod
¶
from_iter(stations: Iterable[Station]) -> StationCollection
filter
¶
filter(*, bbox: BBox | None = None, city: str | Iterable[str] | None = None, province: str | Iterable[str] | None = None, community: str | Iterable[str] | None = None, source: str | Iterable[str] | None = None, active: bool | None = None, has_wmo: bool | None = None) -> StationCollection
Return a new collection keeping stations matching all filters.
String-valued filters accept either a single value or an iterable of values (OR-semantics within a filter, AND across filters). Matching is case-insensitive on the admin fields so users don't have to remember whether AEMET capitalises "Cantabria" or not.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/station.py
within
¶
within(bbox: BBox) -> StationCollection
nearest
¶
nearest(point: tuple[float, float], n: int = 1) -> StationCollection
Return the n stations closest to point = (lon, lat).
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/station.py
ids
¶
cities
¶
provinces
¶
communities
¶
to_dataframe
¶
Return the collection as a :class:pandas.DataFrame.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/station.py
TimeRange
dataclass
¶
Inclusive time window, optionally with a target sampling frequency.
freq is a pandas offset alias ("1D", "6H" ...) used only
by serializers that need to materialize the range (e.g. the CDS
year/month/day form). Adapters accepting continuous ranges
(CMEMS) ignore freq.
parse
classmethod
¶
parse(start: str | datetime | Timestamp, end: str | datetime | Timestamp, freq: str | None = None) -> TimeRange
Parse heterogeneous inputs into a TimeRange.
Accepts ISO strings, datetime, pandas.Timestamp.
Strings without timezone are treated as UTC.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/time.py
to_index
¶
Materialize the range at :attr:freq (default daily).
as_cds_form
¶
Explode the range into the year/month/day form CDS expects.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/time.py
cds_times
¶
Hours-of-day ("HH:MM") the range touches, for the CDS time key.
Derived from :meth:to_index at :attr:freq (default daily, so
["00:00"]). ERA5-style reanalyses are hourly products and
require this field alongside year/month/day; a finer freq
(e.g. "6h") expands the returned hours accordingly.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/time.py
as_xarray_sel
¶
ValidationReport
dataclass
¶
ValidationReport(issues: list[Issue] = list())
Aggregate validation result with convenience accessors.
raise_if_errors
¶
Raise :class:ValueError if any error-level issues are present.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/validation.py
Variable
dataclass
¶
Variable(name: str, standard_name: str | None = None, long_name: str | None = None, units: str | None = None, aliases: Mapping[str, str] = dict(), valid_range: tuple[float, float] | None = None, dtype: str | None = None, cmap: str | None = None)
CF-aware variable descriptor used across adapters and validators.
The class is an immutable dataclass so instances are hashable and
safe to share; per-source identifiers live in aliases and are
resolved with :meth:for_source.
for_source
¶
Return the identifier this variable uses in source.
Falls back to :attr:name if no alias is registered.
cf_attrs
¶
Return the CF-1.x metadata dict suitable for da.attrs.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/variable.py
all_entries
¶
all_entries() -> dict[str, CatalogEntry]
describe
¶
describe(name: str) -> DatasetInfo
Look up a :class:DatasetInfo by short name.
Source code in packages/xrtoolz-products/src/xrproducts/_src/catalog.py
load_aemet
¶
load_aemet(api_key: str | None = None, path: Path | None = None) -> AEMETCredentials | None
Load AEMET credentials, or None if none could be found.
Resolution order:
- Explicit
api_keyargument. AEMET_API_KEYenvironment variable..envfile in the current working directory (oneAEMET_API_KEY=...line).path(or~/.aemet) parsed as akey: valueconfig.
The .env step matches the most common local-dev workflow
without introducing a python-dotenv dependency — we parse the
single key we need and leave the file alone.
Source code in packages/xrtoolz-products/src/xrproducts/_src/credentials.py
load_cds
¶
load_cds(url: str | None = None, key: str | None = None, path: Path | None = None) -> CDSCredentials | None
Load CDS credentials, or None if none could be found.
Resolution order:
- Explicit
url+keyarguments. - Explicit
pathargument (parsed as akey: valueconfig). When given, this bypasses env vars and.envlookup so the caller has a deterministic override. - Environment variables
CDSAPI_URL+CDSAPI_KEY. .envfile walked up from CWD (CDSAPI_URLandCDSAPI_KEYkeys), matching thepython-dotenvlookup behaviour. A notebook started underdocs/still picks up the project-root.env.~/.cdsapircparsed as akey: valueconfig.
Source code in packages/xrtoolz-products/src/xrproducts/_src/credentials.py
load_cmems
¶
load_cmems(username: str | None = None, password: str | None = None, path: Path | None = None) -> CMEMSCredentials | None
Load CMEMS credentials, or None if none could be found.
Resolution order:
- Explicit
username+passwordarguments. - Environment variables
COPERNICUSMARINE_SERVICE_USERNAME+COPERNICUSMARINE_SERVICE_PASSWORD. .envfile walked up from CWD (same keys), matching thepython-dotenvlookup the CDS / AEMET loaders use, so a notebook started underdocs/still picks up the project-root.env.~/.cmems(orpath) parsed as akey: valueconfig.
Source code in packages/xrtoolz-products/src/xrproducts/_src/credentials.py
open_ch4_inventory
¶
open_ch4_inventory(path: str | Path, *, variable: str | None = None, units: str | None = None, source: str | None = None) -> Dataset
Open a gridded CH₄ emission inventory as a flux in kg m⁻² s⁻¹.
The flux variable is variable or, when omitted, the one data
variable on both lat and lon (latitude / longitude are
renamed first). Its units come from units or its units
attribute and must be an area flux in :data:UNIT_FACTORS; the values
are multiplied by that factor, so with f the factor,
F [kg m⁻² s⁻¹] = f · F_file. Longitudes in [0, 360) are wrapped to
[-180, 180), and both axes are sorted ascending.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
NetCDF file of the inventory (one sector or a total). |
required |
variable
|
str | None
|
Name of the flux variable; inferred when |
None
|
units
|
str | None
|
Units of the flux, overriding the file's |
None
|
source
|
str | None
|
Free-text provenance (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with |
Dataset
|
kg m⁻² s⁻¹, with |
Dataset
|
attributes recording what was converted. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If the units are missing or not an accepted area flux. |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/openers/inventory.py
open_emit_ch4_l2b
¶
Open an EMIT L2B CH4 enhancement scene as a flat CF Dataset.
The raster carries the plume enhancement (ppm m) as either
methane_plume_complex (the distributed variable name) or
ch4_enhancement (the registry alias) — renamed to
ch4_enhancement — and, when present, ch4_uncertainty on
(y, x). With glt_path the scene is
orthorectified through EMIT's geometric lookup table: glt_x /
glt_y hold the 1-based raw-pixel column / row that fills each
orthorectified cell (0 marks no data), and the 2-D lon /
lat of the orthorectified grid come from the GLT file's own
lon / lat variables or, failing that, its GDAL geotransform
attribute (the full six-coefficient affine, rotation terms included).
Without a GLT, 2-D lon / lat are taken from the raster file
itself when it has them. A scalar time variable on the raster
survives orthorectification and becomes the scene timestamp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
NetCDF raster with |
required |
glt_path
|
str | Path | None
|
Optional geometric lookup table NetCDF. |
None
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Scene Dataset on |
Dataset
|
file exposes a timestamp) with 2-D |
Raises:
| Type | Description |
|---|---|
KeyError
|
If neither |
ValueError
|
If the GLT exposes neither |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/openers/emit.py
open_ghgsat_ch4_l2
¶
Open a GHGSat per-plume NetCDF as a flat CF Dataset.
Releases carry xch4 (ppb) and/or ch4_enhancement on (y, x)
with 2-D latitude / longitude; both are renamed through the
registry (ghgsat alias, falling back to the canonical name) and
the geolocation becomes lon / lat coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Per-plume NetCDF file. |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Scene Dataset on |
Dataset
|
file exposes a timestamp) with 2-D |
Raises:
| Type | Description |
|---|---|
KeyError
|
If neither |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/openers/ghgsat.py
open_tropomi_ch4_l2
¶
open_tropomi_ch4_l2(path: str | Path, *, qa_min: float | None = 0.5, keep_groups: bool = False) -> Dataset
Flatten a TROPOMI CH4 L2 granule into one CF Dataset.
The PRODUCT group and its three SUPPORT_DATA children are read
with :func:xarray.open_datatree and merged on the shared swath dims
(time, scanline, ground_pixel) (plus layer / level /
corner where a variable carries them). Variables are renamed
through the registry's tropomi aliases (methane_mixing_ratio
-> xch4, ..._bias_corrected -> xch4_bias_corrected,
..._precision -> xch4_precision, column_averaging_kernel,
methane_profile_apriori -> ch4_profile_apriori,
dry_air_subcolumns, qa_value, surface_pressure -> sp);
pressure_interval keeps its product name. latitude /
longitude become the 2-D lat / lon coordinates.
Time is decoded as in the product: the length-1 time dimension
keeps the granule reference time (PRODUCT/time, seconds since
2010-01-01) as its index coordinate, and the per-scanline
delta_time offset is folded into a scanline_time coordinate on
(time, scanline) (datetime64), replacing delta_time. The
reference index is what multi-granule concatenation and
sel(time=...) use; scanline_time is the measurement time of
each scanline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the |
required |
qa_min
|
float | None
|
Pixels with |
0.5
|
keep_groups
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Flat Dataset on ``(time, scanline, ground_pixel[, layer, level, |
Dataset
|
corner]) |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the file has no |
Source code in packages/xrtoolz-products/src/xrproducts/_src/local/openers/tropomi.py
apply_cf_attrs
¶
apply_cf_attrs(da: DataArray, variable: str | Variable, *, overwrite: bool = False) -> DataArray
Stamp CF attributes from variable onto da.
By default, existing attrs are kept. Set overwrite=True to
replace them with the canonical values from the registry.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/validation.py
register
¶
Add variable to :data:REGISTRY (keyed on :attr:Variable.name).
resolve
¶
Return a :class:Variable given an instance or its canonical name.
Raises:
| Type | Description |
|---|---|
KeyError
|
if |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/variable.py
subset_bbox
¶
subset_bbox(ds: Dataset, lon_bnds: tuple[float, float], lat_bnds: tuple[float, float], lon: str = 'lon', lat: str = 'lat') -> Dataset
Keep points whose (lon, lat) fall inside a bounding box.
Works with both 1-D rectilinear grids (where sel with slices would
also work) and 2-D lon/lat coordinate arrays, because it masks via
where(..., drop=True).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
lon_bnds
|
tuple[float, float]
|
|
required |
lat_bnds
|
tuple[float, float]
|
|
required |
lon
|
str
|
Name of the longitude coordinate. |
'lon'
|
lat
|
str
|
Name of the latitude coordinate. |
'lat'
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset restricted to the bounding box. |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/subset.py
subset_time
¶
subset_time(ds: Dataset, time_min: str | datetime64 | Timestamp, time_max: str | datetime64 | Timestamp, time: str = 'time') -> Dataset
Restrict a dataset to the time range [time_min, time_max].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
time_min
|
str | datetime64 | Timestamp
|
Lower bound, any type xarray accepts in a |
required |
time_max
|
str | datetime64 | Timestamp
|
Upper bound, same types as |
required |
time
|
str
|
Name of the time coordinate. |
'time'
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset sliced along the time dimension. |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/subset.py
subset_where
¶
subset_where(ds: Dataset, variable: str, min_val: float, max_val: float, drop: bool = True) -> Dataset
Keep points where variable is in [min_val, max_val].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
variable
|
str
|
Name of the variable used to build the mask. |
required |
min_val
|
float
|
Lower bound (inclusive). |
required |
max_val
|
float
|
Upper bound (inclusive). |
required |
drop
|
bool
|
Whether to drop cells outside the range (default |
True
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with cells outside the range masked or dropped. |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/subset.py
validate_dataset
¶
validate_dataset(ds: Dataset, variables: list[str | Variable], **kwargs: bool) -> ValidationReport
Validate multiple variables in a dataset.
Missing variables are reported as errors; present ones are checked
via :func:validate_variable. Keyword arguments are forwarded.
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/validation.py
validate_variable
¶
validate_variable(da: DataArray, variable: str | Variable, *, check_range: bool = True, check_units: bool = True, check_standard_name: bool = True, check_dtype: bool = False) -> ValidationReport
Validate a single :class:xarray.DataArray against a :class:Variable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
da
|
DataArray
|
Array under test. |
required |
variable
|
str | Variable
|
Either a :class: |
required |
check_range
|
bool
|
If |
True
|
check_units
|
bool
|
Compare |
True
|
check_standard_name
|
bool
|
Compare |
True
|
check_dtype
|
bool
|
Require |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
ValidationReport
|
class: |
Source code in packages/xrtoolz-products/src/xrproducts/types/_src/validation.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |