lex-api-mx-client 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lex_api_mx/__init__.py
ADDED
lex_api_mx/client.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LexApiError(RuntimeError):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
status_code: int,
|
|
14
|
+
message: str,
|
|
15
|
+
request_id: str | None = None,
|
|
16
|
+
body: Any = None,
|
|
17
|
+
):
|
|
18
|
+
self.status_code = status_code
|
|
19
|
+
self.request_id = request_id
|
|
20
|
+
self.detail = message
|
|
21
|
+
self.body = body
|
|
22
|
+
suffix = f" request_id={request_id}" if request_id else ""
|
|
23
|
+
super().__init__(f"Lex API MX returned {status_code}: {message}{suffix}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LexApiClient:
|
|
27
|
+
"""Synchronous client for the stable Lex API MX integration surface."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
base_url: str,
|
|
33
|
+
api_key: str,
|
|
34
|
+
timeout: float = 30.0,
|
|
35
|
+
transport: httpx.BaseTransport | None = None,
|
|
36
|
+
user_agent: str | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
if not base_url.strip():
|
|
39
|
+
raise ValueError("base_url is required")
|
|
40
|
+
if not api_key.strip():
|
|
41
|
+
raise ValueError("api_key is required")
|
|
42
|
+
if user_agent is None:
|
|
43
|
+
from . import __version__ # importación tardía: el paquete importa este módulo
|
|
44
|
+
|
|
45
|
+
user_agent = f"lex-api-mx-python/{__version__}"
|
|
46
|
+
self._client = httpx.Client(
|
|
47
|
+
base_url=base_url.rstrip("/"),
|
|
48
|
+
timeout=timeout,
|
|
49
|
+
transport=transport,
|
|
50
|
+
headers={
|
|
51
|
+
"X-API-Key": api_key,
|
|
52
|
+
"Accept": "application/json",
|
|
53
|
+
"User-Agent": user_agent,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def __enter__(self) -> "LexApiClient":
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, *_: object) -> None:
|
|
61
|
+
self.close()
|
|
62
|
+
|
|
63
|
+
def close(self) -> None:
|
|
64
|
+
self._client.close()
|
|
65
|
+
|
|
66
|
+
def health(self) -> dict[str, Any]:
|
|
67
|
+
return self._request("GET", "/health")
|
|
68
|
+
|
|
69
|
+
def resolve(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
query: str,
|
|
73
|
+
norm: str | None = None,
|
|
74
|
+
jurisdiction: str = "federal",
|
|
75
|
+
as_of: date | str | None = None,
|
|
76
|
+
limit: int = 10,
|
|
77
|
+
idempotency_key: str | None = None,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
payload: dict[str, Any] = {
|
|
80
|
+
"query": query,
|
|
81
|
+
"jurisdiction": jurisdiction,
|
|
82
|
+
"limit": limit,
|
|
83
|
+
}
|
|
84
|
+
if norm is not None:
|
|
85
|
+
payload["norm"] = norm
|
|
86
|
+
if as_of is not None:
|
|
87
|
+
payload["date"] = as_of.isoformat() if isinstance(as_of, date) else as_of
|
|
88
|
+
return self._request(
|
|
89
|
+
"POST",
|
|
90
|
+
"/api/v1/resolve",
|
|
91
|
+
json=payload,
|
|
92
|
+
idempotency_key=idempotency_key,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def map(
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
query: str,
|
|
99
|
+
jurisdiction: str | None = None,
|
|
100
|
+
as_of: date | str | None = None,
|
|
101
|
+
mode: str = "api",
|
|
102
|
+
idempotency_key: str | None = None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
payload: dict[str, Any] = {"query": query, "mode": mode}
|
|
105
|
+
if jurisdiction is not None:
|
|
106
|
+
payload["jurisdiction"] = jurisdiction
|
|
107
|
+
if as_of is not None:
|
|
108
|
+
payload["date"] = as_of.isoformat() if isinstance(as_of, date) else as_of
|
|
109
|
+
return self._request(
|
|
110
|
+
"POST",
|
|
111
|
+
"/api/v1/map",
|
|
112
|
+
json=payload,
|
|
113
|
+
idempotency_key=idempotency_key,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def search_articles(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
query: str,
|
|
120
|
+
norm: str | None = None,
|
|
121
|
+
matter: str | None = None,
|
|
122
|
+
jurisdiction: str | None = None,
|
|
123
|
+
as_of: date | str | None = None,
|
|
124
|
+
limit: int = 20,
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
params: dict[str, Any] = {"q": query, "limit": limit}
|
|
127
|
+
if norm is not None:
|
|
128
|
+
params["norma"] = norm
|
|
129
|
+
if matter is not None:
|
|
130
|
+
params["materia"] = matter
|
|
131
|
+
if jurisdiction is not None:
|
|
132
|
+
params["jurisdiccion"] = jurisdiction
|
|
133
|
+
if as_of is not None:
|
|
134
|
+
params["fecha"] = as_of.isoformat() if isinstance(as_of, date) else as_of
|
|
135
|
+
return self._request("GET", "/api/v1/articulos/search", params=params)
|
|
136
|
+
|
|
137
|
+
def norm_structure(
|
|
138
|
+
self,
|
|
139
|
+
norm: str,
|
|
140
|
+
*,
|
|
141
|
+
as_of: date | str | None = None,
|
|
142
|
+
include_text: bool = False,
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
if not norm.strip():
|
|
145
|
+
raise ValueError("norm is required")
|
|
146
|
+
params: dict[str, Any] = {"include_text": str(include_text).lower()}
|
|
147
|
+
if as_of is not None:
|
|
148
|
+
params["fecha"] = as_of.isoformat() if isinstance(as_of, date) else as_of
|
|
149
|
+
return self._request(
|
|
150
|
+
"GET",
|
|
151
|
+
f"/api/v1/normas/{quote(norm.strip(), safe='')}/estructura",
|
|
152
|
+
params=params,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _request(
|
|
156
|
+
self,
|
|
157
|
+
method: str,
|
|
158
|
+
path: str,
|
|
159
|
+
*,
|
|
160
|
+
json: dict[str, Any] | None = None,
|
|
161
|
+
params: dict[str, Any] | None = None,
|
|
162
|
+
idempotency_key: str | None = None,
|
|
163
|
+
) -> dict[str, Any]:
|
|
164
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
165
|
+
response = self._client.request(method, path, json=json, params=params, headers=headers)
|
|
166
|
+
if response.is_error:
|
|
167
|
+
body: Any = None
|
|
168
|
+
try:
|
|
169
|
+
body = response.json()
|
|
170
|
+
message = str(body.get("detail", body)) if isinstance(body, dict) else str(body)
|
|
171
|
+
except ValueError:
|
|
172
|
+
message = response.text or response.reason_phrase
|
|
173
|
+
raise LexApiError(
|
|
174
|
+
response.status_code,
|
|
175
|
+
message,
|
|
176
|
+
response.headers.get("X-Request-ID"),
|
|
177
|
+
body,
|
|
178
|
+
)
|
|
179
|
+
try:
|
|
180
|
+
value = response.json()
|
|
181
|
+
except ValueError as exc:
|
|
182
|
+
raise LexApiError(
|
|
183
|
+
response.status_code,
|
|
184
|
+
"response was not valid JSON",
|
|
185
|
+
response.headers.get("X-Request-ID"),
|
|
186
|
+
) from exc
|
|
187
|
+
if not isinstance(value, dict):
|
|
188
|
+
raise LexApiError(
|
|
189
|
+
response.status_code,
|
|
190
|
+
"response JSON must be an object",
|
|
191
|
+
response.headers.get("X-Request-ID"),
|
|
192
|
+
)
|
|
193
|
+
return value
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lex-api-mx-client
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Cliente Python de Lex API MX: legislación federal mexicana vigente con fuente, checksum y vigencia
|
|
5
|
+
Project-URL: Homepage, https://lexapi.mx
|
|
6
|
+
Project-URL: Documentation, https://lexapi.mx/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/Julio-Patron/Lex-API-Mx
|
|
8
|
+
Author-email: Lex API MX <admin@lexapi.mx>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,legal,legislacion,leyes,mexico,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: Spanish
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: httpx>=0.28
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Lex API MX — cliente Python
|
|
26
|
+
|
|
27
|
+
Cliente mínimo de [Lex API MX](https://lexapi.mx) para consultar la legislación
|
|
28
|
+
federal mexicana vigente. Cada respuesta trae la fuente oficial, el checksum
|
|
29
|
+
SHA-256 del texto y su periodo de vigencia.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install lex-api-mx-client
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from lex_api_mx import LexApiClient, LexApiError
|
|
37
|
+
|
|
38
|
+
with LexApiClient(base_url="https://api.lexapi.mx", api_key="tu_clave") as lex:
|
|
39
|
+
paquete = lex.resolve(query="Artículo 47 LFT", as_of="2026-10-08")
|
|
40
|
+
articulos = lex.search_articles(query="aguinaldo", norm="lft", jurisdiction="federal")
|
|
41
|
+
arbol = lex.norm_structure("lft")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Método | Endpoint |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `resolve(query, norm=None, jurisdiction="federal", as_of=None, limit=10)` | `POST /api/v1/resolve` |
|
|
47
|
+
| `search_articles(query, norm=None, matter=None, jurisdiction=None, as_of=None, limit=20)` | `GET /api/v1/articulos/search` |
|
|
48
|
+
| `norm_structure(norm, as_of=None, include_text=False)` | `GET /api/v1/normas/{slug}/estructura` |
|
|
49
|
+
| `map(query, jurisdiction=None, as_of=None)` | `POST /api/v1/map` (requiere el permiso `map:read`) |
|
|
50
|
+
|
|
51
|
+
`resolve` y `map` aceptan `idempotency_key`: un reintento con la misma clave no
|
|
52
|
+
se cobra dos veces. Los errores HTTP se lanzan como `LexApiError` con
|
|
53
|
+
`status_code`, `detail`, `request_id` y `body`; `402` significa saldo agotado.
|
|
54
|
+
|
|
55
|
+
Para obtener una clave, crea tu cuenta en
|
|
56
|
+
[lexapi.mx/#cuenta](https://lexapi.mx/#cuenta). Referencia de la API:
|
|
57
|
+
[lexapi.mx/docs](https://lexapi.mx/docs).
|
|
58
|
+
|
|
59
|
+
Para usar Lex API MX desde Claude, Cursor u otro agente, instala el servidor MCP
|
|
60
|
+
[`lex-api-mx-mcp`](https://pypi.org/project/lex-api-mx-mcp/).
|
|
61
|
+
|
|
62
|
+
Código fuente: [github.com/Julio-Patron/Lex-API-Mx](https://github.com/Julio-Patron/Lex-API-Mx/tree/main/packages/lex-api-client).
|
|
63
|
+
Licencia MIT.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
lex_api_mx/__init__.py,sha256=sKfo4DlpEAhqy6RJDJB3G64pskMnlmgIyfxx5-xx8HA,111
|
|
2
|
+
lex_api_mx/client.py,sha256=ADgmKlE_XE4Q8mzoYfyNUOoX85ocnV3yBS41MVDXMpk,6084
|
|
3
|
+
lex_api_mx_client-0.1.1.dist-info/METADATA,sha256=3jfZlnk11HbUZOLJ3JQnKhDjoZX2cNQrRHm_-bQIH_8,2757
|
|
4
|
+
lex_api_mx_client-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
+
lex_api_mx_client-0.1.1.dist-info/licenses/LICENSE,sha256=PlLjYKAY2g9heAxRGS-jSHErrfin7_XElwcGIy1wZEg,1067
|
|
6
|
+
lex_api_mx_client-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lex API MX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|