|
38 | 38 | SAMPLES_URL, |
39 | 39 | _accept_legacy_kwargs, |
40 | 40 | _as_str_list, |
| 41 | + _check_ogc_requests, |
41 | 42 | _check_profiles, |
42 | 43 | _construct_cql_request, |
43 | 44 | _default_headers, |
@@ -74,6 +75,7 @@ def get_daily( |
74 | 75 | filter: str | None = None, |
75 | 76 | filter_lang: FILTER_LANG | None = None, |
76 | 77 | convert_type: bool = True, |
| 78 | + **queryables: Any, |
77 | 79 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
78 | 80 | """Daily data provide one data value to represent water conditions for the |
79 | 81 | day. |
@@ -206,6 +208,10 @@ def get_daily( |
206 | 208 | and the lexicographic-comparison pitfall. |
207 | 209 | convert_type : boolean, optional |
208 | 210 | If True, converts columns to appropriate types. |
| 211 | + **queryables : string or iterable of strings, optional |
| 212 | + Any other queryable property of this collection, passed through as a |
| 213 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 214 | + collection supports. |
209 | 215 |
|
210 | 216 | Returns |
211 | 217 | ------- |
@@ -295,6 +301,7 @@ def get_continuous( |
295 | 301 | filter: str | None = None, |
296 | 302 | filter_lang: FILTER_LANG | None = None, |
297 | 303 | convert_type: bool = True, |
| 304 | + **queryables: Any, |
298 | 305 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
299 | 306 | """ |
300 | 307 | Continuous data provide instantaneous water conditions. |
@@ -421,6 +428,10 @@ def get_continuous( |
421 | 428 | and the lexicographic-comparison pitfall. |
422 | 429 | convert_type : boolean, optional |
423 | 430 | If True, converts columns to appropriate types. |
| 431 | + **queryables : string or iterable of strings, optional |
| 432 | + Any other queryable property of this collection, passed through as a |
| 433 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 434 | + collection supports. |
424 | 435 |
|
425 | 436 | Returns |
426 | 437 | ------- |
@@ -520,6 +531,7 @@ def get_monitoring_locations( |
520 | 531 | filter: str | None = None, |
521 | 532 | filter_lang: FILTER_LANG | None = None, |
522 | 533 | convert_type: bool = True, |
| 534 | + **queryables: Any, |
523 | 535 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
524 | 536 | """Location information is basic information about the monitoring location |
525 | 537 | including the name, identifier, agency responsible for data collection, and |
@@ -738,6 +750,10 @@ def get_monitoring_locations( |
738 | 750 | and the lexicographic-comparison pitfall. |
739 | 751 | convert_type : boolean, optional |
740 | 752 | If True, converts columns to appropriate types. |
| 753 | + **queryables : string or iterable of strings, optional |
| 754 | + Any other queryable property of this collection, passed through as a |
| 755 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 756 | + collection supports. |
741 | 757 |
|
742 | 758 | Returns |
743 | 759 | ------- |
@@ -808,6 +824,7 @@ def get_time_series_metadata( |
808 | 824 | filter: str | None = None, |
809 | 825 | filter_lang: FILTER_LANG | None = None, |
810 | 826 | convert_type: bool = True, |
| 827 | + **queryables: Any, |
811 | 828 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
812 | 829 | """Daily data and continuous measurements are grouped into time series, |
813 | 830 | which represent a collection of observations of a single parameter, |
@@ -975,6 +992,10 @@ def get_time_series_metadata( |
975 | 992 | and the lexicographic-comparison pitfall. |
976 | 993 | convert_type : boolean, optional |
977 | 994 | If True, converts columns to appropriate types. |
| 995 | + **queryables : string or iterable of strings, optional |
| 996 | + Any other queryable property of this collection, passed through as a |
| 997 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 998 | + collection supports. |
978 | 999 |
|
979 | 1000 | Returns |
980 | 1001 | ------- |
@@ -1080,6 +1101,7 @@ def get_combined_metadata( |
1080 | 1101 | filter: str | None = None, |
1081 | 1102 | filter_lang: FILTER_LANG | None = None, |
1082 | 1103 | convert_type: bool = True, |
| 1104 | + **queryables: Any, |
1083 | 1105 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
1084 | 1106 | """Get combined monitoring-location and time-series metadata. |
1085 | 1107 |
|
@@ -1182,6 +1204,10 @@ def get_combined_metadata( |
1182 | 1204 | and the lexicographic-comparison pitfall. |
1183 | 1205 | convert_type : boolean, optional |
1184 | 1206 | If True, converts columns to appropriate types. |
| 1207 | + **queryables : string or iterable of strings, optional |
| 1208 | + Any other queryable property of this collection, passed through as a |
| 1209 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 1210 | + collection supports. |
1185 | 1211 |
|
1186 | 1212 | Returns |
1187 | 1213 | ------- |
@@ -1277,6 +1303,7 @@ def get_latest_continuous( |
1277 | 1303 | filter: str | None = None, |
1278 | 1304 | filter_lang: FILTER_LANG | None = None, |
1279 | 1305 | convert_type: bool = True, |
| 1306 | + **queryables: Any, |
1280 | 1307 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
1281 | 1308 | """This endpoint provides the most recent observation for each time series |
1282 | 1309 | of continuous data. Continuous data are collected via automated sensors |
@@ -1406,6 +1433,10 @@ def get_latest_continuous( |
1406 | 1433 | and the lexicographic-comparison pitfall. |
1407 | 1434 | convert_type : boolean, optional |
1408 | 1435 | If True, converts columns to appropriate types. |
| 1436 | + **queryables : string or iterable of strings, optional |
| 1437 | + Any other queryable property of this collection, passed through as a |
| 1438 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 1439 | + collection supports. |
1409 | 1440 |
|
1410 | 1441 | Returns |
1411 | 1442 | ------- |
@@ -1478,6 +1509,7 @@ def get_latest_daily( |
1478 | 1509 | filter: str | None = None, |
1479 | 1510 | filter_lang: FILTER_LANG | None = None, |
1480 | 1511 | convert_type: bool = True, |
| 1512 | + **queryables: Any, |
1481 | 1513 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
1482 | 1514 | """Daily data provide one data value to represent water conditions for the |
1483 | 1515 | day. |
@@ -1609,6 +1641,10 @@ def get_latest_daily( |
1609 | 1641 | and the lexicographic-comparison pitfall. |
1610 | 1642 | convert_type : boolean, optional |
1611 | 1643 | If True, converts columns to appropriate types. |
| 1644 | + **queryables : string or iterable of strings, optional |
| 1645 | + Any other queryable property of this collection, passed through as a |
| 1646 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 1647 | + collection supports. |
1612 | 1648 |
|
1613 | 1649 | Returns |
1614 | 1650 | ------- |
@@ -1682,6 +1718,7 @@ def get_field_measurements( |
1682 | 1718 | filter: str | None = None, |
1683 | 1719 | filter_lang: FILTER_LANG | None = None, |
1684 | 1720 | convert_type: bool = True, |
| 1721 | + **queryables: Any, |
1685 | 1722 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
1686 | 1723 | """Field measurements are physically measured values collected during a |
1687 | 1724 | visit to the monitoring location. Field measurements consist of measurements |
@@ -1804,6 +1841,10 @@ def get_field_measurements( |
1804 | 1841 | and the lexicographic-comparison pitfall. |
1805 | 1842 | convert_type : boolean, optional |
1806 | 1843 | If True, converts columns to appropriate types. |
| 1844 | + **queryables : string or iterable of strings, optional |
| 1845 | + Any other queryable property of this collection, passed through as a |
| 1846 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 1847 | + collection supports. |
1807 | 1848 |
|
1808 | 1849 | Returns |
1809 | 1850 | ------- |
@@ -1873,6 +1914,7 @@ def get_field_measurements_metadata( |
1873 | 1914 | filter: str | None = None, |
1874 | 1915 | filter_lang: FILTER_LANG | None = None, |
1875 | 1916 | convert_type: bool = True, |
| 1917 | + **queryables: Any, |
1876 | 1918 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
1877 | 1919 | """Get field-measurement metadata: one row per (location, parameter) series. |
1878 | 1920 |
|
@@ -1926,6 +1968,10 @@ def get_field_measurements_metadata( |
1926 | 1968 | and the lexicographic-comparison pitfall. |
1927 | 1969 | convert_type : boolean, optional |
1928 | 1970 | If True, converts columns to appropriate types. |
| 1971 | + **queryables : string or iterable of strings, optional |
| 1972 | + Any other queryable property of this collection, passed through as a |
| 1973 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 1974 | + collection supports. |
1929 | 1975 |
|
1930 | 1976 | Returns |
1931 | 1977 | ------- |
@@ -1998,6 +2044,7 @@ def get_peaks( |
1998 | 2044 | filter: str | None = None, |
1999 | 2045 | filter_lang: FILTER_LANG | None = None, |
2000 | 2046 | convert_type: bool = True, |
| 2047 | + **queryables: Any, |
2001 | 2048 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
2002 | 2049 | """Get the annual peak streamflow / stage record for a monitoring location. |
2003 | 2050 |
|
@@ -2056,6 +2103,10 @@ def get_peaks( |
2056 | 2103 | and the lexicographic-comparison pitfall. |
2057 | 2104 | convert_type : boolean, optional |
2058 | 2105 | If True, converts columns to appropriate types. |
| 2106 | + **queryables : string or iterable of strings, optional |
| 2107 | + Any other queryable property of this collection, passed through as a |
| 2108 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 2109 | + collection supports. |
2059 | 2110 |
|
2060 | 2111 | Returns |
2061 | 2112 | ------- |
@@ -2198,6 +2249,68 @@ def get_reference_table( |
2198 | 2249 | ) |
2199 | 2250 |
|
2200 | 2251 |
|
| 2252 | +def get_queryables(collection: str) -> tuple[pd.DataFrame, BaseMetadata]: |
| 2253 | + """List the queryable properties of a Water Data API collection. |
| 2254 | +
|
| 2255 | + Every OGC collection (``daily``, ``continuous``, ``monitoring-locations``, |
| 2256 | + ...) advertises the set of properties that can be filtered on -- exposed as |
| 2257 | + the typed keyword arguments of the matching ``get_*`` function, and usable |
| 2258 | + directly in a CQL2 ``filter``. This returns that set, so the available |
| 2259 | + filters can be discovered programmatically and monitored for upstream |
| 2260 | + additions. |
| 2261 | +
|
| 2262 | + Parameters |
| 2263 | + ---------- |
| 2264 | + collection : string |
| 2265 | + The collection id, e.g. ``"daily"``, ``"continuous"``, |
| 2266 | + ``"monitoring-locations"``, or ``"time-series-metadata"``. See |
| 2267 | + :data:`dataretrieval.waterdata.types.WATERDATA_SERVICES` for the data |
| 2268 | + collections; reference collections (e.g. ``"parameter-codes"``) work |
| 2269 | + too. |
| 2270 | +
|
| 2271 | + Returns |
| 2272 | + ------- |
| 2273 | + df : ``pandas.DataFrame`` |
| 2274 | + One row per queryable, sorted by name, with columns ``queryable`` (the |
| 2275 | + property name), ``type``, ``title``, and ``description``. |
| 2276 | + md : :class:`dataretrieval.utils.BaseMetadata` |
| 2277 | + Metadata describing the request (URL, query time, response headers). |
| 2278 | +
|
| 2279 | + Raises |
| 2280 | + ------ |
| 2281 | + DataRetrievalError |
| 2282 | + On an HTTP error response (e.g. an unknown ``collection`` yields a 404), |
| 2283 | + the typed subclass for the status. |
| 2284 | +
|
| 2285 | + Examples |
| 2286 | + -------- |
| 2287 | + .. doctest:: |
| 2288 | + :skipif: True # network |
| 2289 | +
|
| 2290 | + >>> from dataretrieval import waterdata |
| 2291 | + >>> df, md = waterdata.get_queryables("daily") |
| 2292 | + >>> df.set_index("queryable").loc["state_name", "type"] |
| 2293 | + 'string' |
| 2294 | + """ |
| 2295 | + # The OGC queryables document is a JSON Schema whose ``properties`` map each |
| 2296 | + # filterable property name to a ``{title, type, description}`` definition. |
| 2297 | + body, response = _check_ogc_requests(endpoint=collection, req_type="queryables") |
| 2298 | + properties: dict[str, Any] = body.get("properties", {}) |
| 2299 | + df = pd.DataFrame( |
| 2300 | + [ |
| 2301 | + { |
| 2302 | + "queryable": name, |
| 2303 | + "type": prop.get("type"), |
| 2304 | + "title": prop.get("title"), |
| 2305 | + "description": (prop.get("description") or "").strip(), |
| 2306 | + } |
| 2307 | + for name, prop in sorted(properties.items()) |
| 2308 | + ], |
| 2309 | + columns=["queryable", "type", "title", "description"], |
| 2310 | + ) |
| 2311 | + return df, BaseMetadata(response) |
| 2312 | + |
| 2313 | + |
2201 | 2314 | def get_codes(code_service: CODE_SERVICES) -> tuple[pd.DataFrame, BaseMetadata]: |
2202 | 2315 | """Return codes from a Samples code service. |
2203 | 2316 |
|
@@ -2916,6 +3029,7 @@ def get_channel( |
2916 | 3029 | filter: str | None = None, |
2917 | 3030 | filter_lang: FILTER_LANG | None = None, |
2918 | 3031 | convert_type: bool = True, |
| 3032 | + **queryables: Any, |
2919 | 3033 | ) -> tuple[pd.DataFrame, BaseMetadata]: |
2920 | 3034 | """ |
2921 | 3035 | Channel measurements taken as part of streamflow field measurements. |
@@ -3045,6 +3159,10 @@ def get_channel( |
3045 | 3159 | and the lexicographic-comparison pitfall. |
3046 | 3160 | convert_type : boolean, optional |
3047 | 3161 | If True, converts columns to appropriate types. |
| 3162 | + **queryables : string or iterable of strings, optional |
| 3163 | + Any other queryable property of this collection, passed through as a |
| 3164 | + server-side filter. Call :func:`get_queryables` to see the queryables a |
| 3165 | + collection supports. |
3048 | 3166 |
|
3049 | 3167 | Returns |
3050 | 3168 | ------- |
|
0 commit comments