Metadata-Version: 2.4
Name: legalize
Version: 0.2.0
Summary: Official Python client for the Legalize API
Project-URL: Homepage, https://legalize.dev
Project-URL: Documentation, https://legalize.dev/api/docs
Project-URL: Repository, https://github.com/legalize-dev/legalize-sdks
Project-URL: Issues, https://github.com/legalize-dev/legalize-sdks/issues
Author-email: Legalize <dev@legalize.dev>
License: MIT License
        
        Copyright (c) 2026 Legalize
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: api,laws,legal,legalize,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: datamodel-code-generator>=0.26; extra == 'dev'
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: schemathesis>=3.30; extra == 'dev'
Requires-Dist: vcrpy>=6; extra == 'dev'
Description-Content-Type: text/markdown

# legalize

[![python-ci](https://github.com/legalize-dev/legalize-sdks/actions/workflows/python-ci.yml/badge.svg)](https://github.com/legalize-dev/legalize-sdks/actions/workflows/python-ci.yml)
[![PyPI](https://img.shields.io/pypi/v/legalize.svg)](https://pypi.org/project/legalize/)
[![Python versions](https://img.shields.io/pypi/pyversions/legalize.svg)](https://pypi.org/project/legalize/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/legalize-dev/legalize-sdks/blob/main/LICENSE)

Official Python client for the [Legalize API](https://legalize.dev/api) — legal texts as structured, versioned data.

```bash
pip install legalize
```

```python
from legalize import Legalize

client = Legalize(api_key="leg_...")

for law in client.laws.iter(country="es", law_type="ley_organica"):
    print(law.id, law.title)
```

## Why this SDK

- **Typed end-to-end.** Pydantic v2 models generated from the canonical
  OpenAPI spec. `py.typed` ships in the wheel. `mypy --strict` clean.
- **Sync by default, async when you need it.** `Legalize` and
  `AsyncLegalize` expose the same resource API and error types — swap
  one for the other without rewriting your call sites.
- **Retries with backoff built in.** Honors `Retry-After`, handles
  429/5xx, exponential delay with jitter, all configurable.
- **Webhook verification is a one-liner.** Constant-time HMAC compare,
  5-minute anti-replay window, clock-skew tolerant.
- **No magic, no frameworks.** One client, one method per endpoint.

## Quick tour

### List, iterate, search

```python
# One page
page = client.laws.list(country="es", page=1, per_page=50)
print(page.total, len(page.items))

# Auto-paginated iterator (fetches pages as needed)
for law in client.laws.iter(country="es", status="vigente"):
    ...

# Full-text search
results = client.laws.search(country="es", q="protección de datos")
```

### Time-travel

Every law has a git-tracked history. Retrieve it at any past revision:

```python
commits = client.laws.commits(country="es", law_id="ley_organica_3_2018")
past = client.laws.at_commit(
    country="es",
    law_id="ley_organica_3_2018",
    sha=commits.items[-1].sha,
)
print(past.content)  # Markdown at that revision
```

### XML (and other raw formats)

The typed methods always return JSON-parsed models. When your app speaks
XML, use `request_raw` to fetch any endpoint in another wire format via
content negotiation — it sets `Accept` and hands you the body untouched:

```python
res = client.request_raw("GET", "/api/v1/es/laws/BOE-A-1978-31229")
res.content_type        # "application/xml; charset=utf-8"
xml_text = res.text     # the raw XML string
root = res.xml()        # parsed into an ElementTree element

# format="json" or any explicit media type works the same way:
data = client.request_raw("GET", "/api/v1/countries", format="json").json()
```

`request_raw` defaults to `format="xml"`. Errors raise the same typed
exceptions as the JSON methods (the error body is in the negotiated
format). See the
[Response formats](https://legalize.dev/docs/formats) docs.

### Async

```python
import asyncio
from legalize import AsyncLegalize

async def main():
    async with AsyncLegalize(api_key="leg_...") as client:
        page = await client.laws.list(country="es")
        async for law in client.laws.iter(country="fr"):
            print(law.id)

asyncio.run(main())
```

### Webhooks

Verify a signed delivery in one call:

```python
from legalize import Webhook, WebhookVerificationError

try:
    event = Webhook.verify(
        payload=request.body,                              # raw bytes
        sig_header=request.headers["X-Legalize-Signature"],
        timestamp=request.headers["X-Legalize-Timestamp"],
        secret=os.environ["LEGALIZE_WHSEC"],
    )
except WebhookVerificationError:
    return Response(status_code=400)

if event.type == "law.updated":
    ...
```

Working Flask and FastAPI receivers in
[`examples/`](https://github.com/legalize-dev/legalize-sdks/tree/main/python/examples).

## Configuration

### Zero-config (recommended for servers + Kubernetes)

Set the environment and just instantiate:

```bash
export LEGALIZE_API_KEY=leg_live_...
# Optional:
export LEGALIZE_BASE_URL=https://legalize.dev
export LEGALIZE_API_VERSION=v1
```

```python
from legalize import Legalize

client = Legalize()   # picks everything up from the environment
```

### Explicit

```python
from legalize import Legalize, RetryPolicy

client = Legalize(
    api_key="leg_...",
    base_url="https://legalize.dev",
    api_version="v1",              # negotiated via Legalize-API-Version
    timeout=30.0,
    retry=RetryPolicy(max_retries=5, initial_delay=0.5, max_delay=10.0),
    default_headers={"X-Correlation-Id": "..."},
)
```

Precedence: explicit argument > environment variable > built-in default.
The full cross-SDK contract is documented in
[`ENVIRONMENT.md`](https://github.com/legalize-dev/legalize-sdks/blob/main/ENVIRONMENT.md).

Read rate-limit headers from the last response:

```python
client.countries.list()
resp = client.last_response
print(resp.headers.get("X-RateLimit-Remaining"))
```

## Errors

All errors inherit from `LegalizeError`. Catch the specific one you care
about and let the rest bubble:

```python
from legalize import (
    AuthenticationError,   # 401 — bad/missing key
    ForbiddenError,        # 403
    NotFoundError,         # 404
    InvalidRequestError,   # 400
    ValidationError,       # 422
    RateLimitError,        # 429 — retried automatically by default
    ServerError,           # 5xx
    APIConnectionError,    # network failure
    APITimeoutError,       # timeout
    WebhookVerificationError,
)
```

Every `APIError` exposes `.status_code`, `.code`, `.body`, and
`.response` for debugging.

## Compatibility

- Python 3.10, 3.11, 3.12, 3.13
- Linux, macOS, Windows
- `httpx` ≥ 0.27, `pydantic` ≥ 2.6

## Links

- [API reference](https://legalize.dev/api/docs)
- [Monorepo](https://github.com/legalize-dev/legalize-sdks) (sources for all language SDKs)
- [Changelog](https://github.com/legalize-dev/legalize-sdks/blob/main/python/CHANGELOG.md)
- [Contributing](https://github.com/legalize-dev/legalize-sdks/blob/main/CONTRIBUTING.md)

## License

MIT
