# Python 3. No packages required. Optional key: DATASCOOP_API_KEY. import json import os import sys from datetime import datetime from urllib.error import HTTPError, URLError from urllib.parse import urlencode from urllib.request import Request, urlopen ENDPOINT = "https://datascoop.io/v1/datasets/cqc/query" DATASET = "cqc" FILTERS = json.loads("{\"careHome\":\"true\",\"localAuthorityExact\":\"Devon\",\"serviceType\":\"Nursing homes\"}") PAGE_SIZE, MAX_ROWS, MAX_PAGES = 25, 2500, 100 HEADERS = {"Accept": "application/json"} key = os.environ.get("DATASCOOP_API_KEY", "").strip() if key: HEADERS["Authorization"] = "Bearer " + key def text_value(value): return "true" if value is True else "false" if value is False else str(value) def retrieve(): rows, total, imported_at, first = [], None, None, None for _ in range(MAX_PAGES): params = dict(FILTERS, strict_filters="1", limit=PAGE_SIZE, offset=len(rows)) request = Request(ENDPOINT + "?" + urlencode(params), headers=HEADERS) with urlopen(request, timeout=30) as response: result = json.load(response) if (not isinstance(result, dict) or result.get("dataset") != DATASET or not isinstance(result.get("rows"), list) or any(not isinstance(row, dict) for row in result["rows"]) or not isinstance(result.get("ignored"), list) or result["ignored"] or type(result.get("total")) is not int or result["total"] < 0 or type(result.get("offset")) is not int or result["offset"] != len(rows) or type(result.get("returned")) is not int or result.get("returned") != len(result["rows"]) or result["returned"] > PAGE_SIZE or type(result.get("truncated")) is not bool): raise ValueError("Unexpected query response; no partial result was printed.") if result["total"] > MAX_ROWS: raise ValueError("More than 2500 rows match. Narrow filters or review the bound.") applied = result.get("applied_filters") if not isinstance(applied, dict): raise ValueError("Applied filters are missing.") for name, value in FILTERS.items(): if name not in applied or text_value(applied[name]) != text_value(value): raise ValueError("A requested filter was not applied: " + name) provenance = result.get("provenance") imported = provenance.get("imported_at") if isinstance(provenance, dict) else None if not isinstance(imported, str): raise ValueError("Import date is missing; no partial result was printed.") datetime.fromisoformat(imported.replace("Z", "+00:00")) if first is None: first, total, imported_at = result, result["total"], imported if result["total"] != total or imported != imported_at: raise ValueError("The import or match count changed. Run the request again.") next_offset = len(rows) + result["returned"] if (next_offset > total or result["truncated"] != (next_offset < total) or (result["returned"] == 0 and next_offset < total)): raise ValueError("Pagination stopped making sense; no partial result was printed.") rows.extend(result["rows"]) if len(rows) == total: return dict(dataset=DATASET, filters=FILTERS, total=total, rows=rows, provenance=first["provenance"], licence=first.get("licence"), attribution=first.get("attribution")) raise ValueError("Page limit reached. Narrow filters; no partial result was printed.") try: result = retrieve() if result["total"] == 0: print("No matching records. Check the selected area and filters.", file=sys.stderr) # Keep provenance and attribution with cached or republished data. print(json.dumps(result, indent=2)) except HTTPError as error: print("Query failed: HTTP " + str(error.code) + ". Check filters and access; retry later for rate limits or server errors.", file=sys.stderr) sys.exit(1) except (URLError, TimeoutError, ValueError, TypeError, KeyError) as error: print(str(error), file=sys.stderr) sys.exit(1)