SDK de TypeScript y Python
SDK de TypeScript y Python
Section titled “SDK de TypeScript y Python”Dos clientes oficiales, de código abierto con licencia MIT, en github.com/normatia/normatia. Cada método llama a una ruta de la API y devuelve su JSON, con el tipo de cada respuesta para el editor. Sirven con la clave de siempre y con una clave de prueba.
| Lenguaje | Paquete | Necesita |
|---|---|---|
| TypeScript y JavaScript | normatia en npm | Node 18 o posterior |
| Python | normatia en PyPI | Python 3.9 o posterior |
Los dos se publican desde ese repositorio con GitHub Actions, sin tokens y con certificado de procedencia: npm y PyPI muestran de qué commit sale cada versión.
Instalación
Section titled “Instalación”npm install normatiaTambién con pnpm add normatia, yarn add normatia o bun add normatia.
pip install normatiaLa clave
Section titled “La clave”Créala en normatia.com, Mi cuenta > Claves API, y guárdala en la variable de entorno NORMATIA_API_KEY. El cliente de Python la lee de ahí; al de TypeScript se le pasa al crearlo. Cómo funcionan las claves, sus permisos y sus límites está en Autenticación.
Primer ejemplo
Section titled “Primer ejemplo”Una parcela con sus afecciones, los datos técnicos de su municipio y las normas municipales que le aplican. Con una clave de prueba funciona tal cual: la parcela está en el entorno de pruebas.
import { NormatiaClient } from "normatia";
const client = new NormatiaClient({ apiKey: process.env.NORMATIA_API_KEY! });
const parcel = await client.getParcel("6963301TG3466N", { include: ["constraints", "zoning"] });for (const layer of parcel.constraints?.layers ?? []) { // null es «sin datos», nunca «no afectada» if (layer.affected) console.log(layer.layer, layer.summary);}
const place = await client.getPlace("ES-41091");console.log(place.technical_data?.climate_zone); // { value: "B4", source: { citation: "CTE DB-HE, Anejo B", ... } }
const { data } = await client.listRegulations({ place: "ES-41091", scope: "municipal" });for (const regulation of data) console.log(regulation.slug, regulation.current_version?.curated);from normatia import Normatia
client = Normatia() # lee NORMATIA_API_KEY
parcel = client.get_parcel("6963301TG3466N", include=["constraints", "zoning"])for layer in parcel["constraints"]["layers"]: if layer["affected"]: # None es «sin datos», nunca «no afectada» print(layer["layer"], layer["summary"])
place = client.get_place("ES-41091")print(place["technical_data"]["climate_zone"]) # {'value': 'B4', 'source': {'citation': 'CTE DB-HE, Anejo B', ...}}
for regulation in client.iter_regulations(place="ES-41091", scope="municipal"): print(regulation["slug"], regulation["current_version"]["curated"])iter_regulations recorre todas las páginas del catálogo. Hay también un cliente asíncrono con los mismos métodos:
from normatia import AsyncNormatia
async with AsyncNormatia() as client: me = await client.get_me()Errores
Section titled “Errores”Cada error de la API llega como una excepción con el código de la lista de errores, que es lo que debe mirar tu programa, y el identificador de la petición, que es lo que nos tienes que mandar si nos escribes.
| Dato | TypeScript | Python |
|---|---|---|
| Estado HTTP | statusCode (0 si falla la red) | status_code |
| Código | code | code |
| Mensaje para una persona | message | message |
| Identificador de la petición | requestId | request_id |
| Segundos que esperar | retryAfter, en RateLimitError | retry_after |
Las clases son las mismas en los dos: AuthenticationError (401), ForbiddenError (403), NotFoundError (404), ValidationError (422) y RateLimitError (429), todas hijas de NormatiaError. El de Python tiene además ServiceUnavailableError (503) y APIError para el resto.
import { NotFoundError } from "normatia";
try { await client.getParcel("4141412VG4444S");} catch (error) { if (error instanceof NotFoundError) console.log(error.code, error.requestId); // parcel_not_found (not_in_sandbox con una clave de prueba), req_... else throw error;}from normatia import NotFoundError
try: client.get_parcel("4141412VG4444S")except NotFoundError as e: print(e.code, e.request_id) # parcel_not_found (not_in_sandbox con una clave de prueba), req_...Qué método llama a cada ruta
Section titled “Qué método llama a cada ruta”| Ruta | TypeScript | Python |
|---|---|---|
GET /v1/me | getMe() | get_me() |
GET /v1/usage | getUsage() | get_usage() |
GET /v1/keys, DELETE /v1/keys/{id} | listKeys(), revokeKey(id) | list_keys(), revoke_key(id) |
GET /v1/parcels/{ref} | getParcel(ref, { include }) | get_parcel(ref, include=[...]) |
GET /v1/parcels?lat=&lon= | findParcelsAtPoint(lat, lon) | find_parcels_at_point(lat, lon) |
GET /v1/sandbox | getSandbox() | get_sandbox() |
GET /v1/layers, GET /v1/coverage | listLayers(), getCoverage(place) | list_layers(), get_coverage(place) |
GET /v1/places, GET /v1/places/{place} | findPlaces({ q }), getPlace(place) | find_places(q), get_place(place) |
GET /v1/regulations | listRegulations() | list_regulations(), iter_regulations() |
GET /v1/regulations/{slug} | getRegulation(slug) | get_regulation(slug) |
GET /v1/regulations/{slug}/contents | getRegulationContents(slug) | get_regulation_contents(slug) |
GET /v1/regulations/{slug}/sections/{section} | getRegulationSection(slug, section) | get_regulation_section(slug, section) |
POST /api/v2/ask | askV2({ query, project_id }) | ask(query, project_id=) |
POST /api/v1/verify | verify({ ... }) | verify(...) |
El de TypeScript tiene también los métodos de las rutas anteriores: searchLocations y getLocation (Location), searchCodes y getCode (Codes), listProjects y getProjectInfo (Projects).
La referencia completa de cada cliente, con todos los parámetros, está en su README: TypeScript y Python.