Metadata-Version: 2.5
Name: macadress
Version: 1.0.0
Summary: Official Python client for the macadress.com MAC address and OUI vendor lookup API.
Project-URL: Homepage, https://macadress.com
Project-URL: Documentation, https://macadress.com/docs
Project-URL: Source, https://github.com/sapisos/macadress-python
Project-URL: Issues, https://github.com/sapisos/macadress-python/issues
Project-URL: Changelog, https://github.com/sapisos/macadress-python/blob/main/CHANGELOG.md
Author: ApisOS FZE
License: MIT
License-File: LICENSE
Keywords: api-client,ieee,mac-address,mac-vendor,macadress,oui,sdk,vendor-lookup
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 :: Only
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: Topic :: Internet
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# macadress

Official Python client for the [macadress.com](https://macadress.com) MAC address
and OUI vendor lookup API.

- Vendor name, OUI, IEEE block, country, address type, EUI-64 / IPv6 link-local, randomization confidence, device guess
- Keyless vendor-name lookup, plus keyed single / batch / directory-search calls
- Typed dataclass results with a `raw` escape hatch, typed exceptions per failure mode
- Sync `Client` and async `AsyncClient` from the same API, one dependency (`httpx`)

```python
from macadress import Client

mac = Client("mk_live_xxx")

mac.vendor("00:03:93:AB:12:34")            # "Apple, Inc."   (no API key required)
mac.lookup("00:03:93:AB:12:34").country    # "US"
```

## Install

```bash
pip install macadress
```

Requires Python 3.10+.

## Getting a key

`vendor()` needs no key. Everything else does. A free key (1,000 lookups a day) is
instant at [macadress.com/signup](https://macadress.com/signup); see
[pricing](https://macadress.com/pricing) for more.

## Usage

### Create a client

```python
from macadress import Client

mac = Client("mk_live_xxx")

# keyless: only vendor() will work
anon = Client()

# options
mac = Client(
    "mk_live_xxx",
    base_url="https://api.macadress.com",   # change only for a self-hosted deployment
    timeout=10.0,
    headers={"X-Trace": "my-app"},
)

# reuse / close
with Client("mk_live_xxx") as mac:
    ...
```

### `vendor()` - name only, no key

Returns `None` when the address is valid but has no vendor to report
(unregistered, private, or locally administered / randomized).

```python
mac.vendor("00:03:93:AB:12:34")   # "Apple, Inc."
mac.vendor("02:1a:2b:3c:4d:5e")   # None
```

`:`, `-`, `.` and space grouping are all accepted, as is a bare 12-hex string.

### `lookup()` - full analysis

```python
r = mac.lookup("3C:22:FB:12:34:56")

r.organization              # str | None
r.vendor_lookup_reliable    # bool  (False for a private block / LAA)
r.oui                       # "3C:22:FB"
r.matched_prefix            # full matched block at its real width
r.block_type                # BlockType.MA_L (compares equal to "MA-L")
r.country                   # "US" | None
r.administration_type       # AdministrationType.UNIVERSALLY_ADMINISTERED | ...
r.potentially_randomized    # bool
r.randomization_confidence  # RandomizationConfidence.NONE | POSSIBLE | LIKELY
r.eui64                     # "3E:22:FB:FF:FE:12:34:56" | None
r.ipv6_link_local           # "fe80::3e22:fbff:fe12:3456" | None
r.device.category           # DeviceCategory.UNKNOWN (usually)
r.explanation               # plain-English summary
r.meta.database_version     # "2026-08-30"
```

Any field not covered by an attribute is still reachable:

```python
r.get("vendor_location.city")   # dotted path into r.raw, default None
r.raw                           # the decoded payload as given
```

### `batch()` - up to 100 at once

Results come back in input order; check each item.

```python
for item in mac.batch(["00:03:93:00:00:00", "3C:22:FB:00:00:00", "bad"]):
    if item.failed:
        print(item.input, "->", item.error)
    else:
        print(item.input, "->", item.organization)
```

Raises `ValueError` (no request made) if the iterable is empty or has more than
`macadress.MAX_BATCH_SIZE` (100) entries.

### `search_vendors()` - the directory

```python
result = mac.search_vendors("Cisco", country="US", limit=20)

result.total   # total matches, ignoring the limit
for block in result:
    print(block.block_type, block.organization, block.country)
```

### `health()`

```python
mac.health()   # bool, keyless, uncounted; a transport failure is False
```

### Async

`AsyncClient` mirrors `Client` method for method:

```python
import asyncio
from macadress import AsyncClient

async def main():
    async with AsyncClient("mk_live_xxx") as mac:
        print(await mac.vendor("00:03:93:AB:12:34"))
        r = await mac.lookup("3C:22:FB:12:34:56")
        print(r.organization)

asyncio.run(main())
```

## Errors

Every failure is a `MacadressError`.

| Class | When |
|---|---|
| `InvalidMACError` | HTTP 400, the input did not parse |
| `AuthenticationError` | HTTP 401, missing or invalid API key |
| `RateLimitError` | HTTP 429, per-minute rate exceeded. `.retry_after` (seconds) when sent |
| `QuotaExceededError` | HTTP 429, billing-cycle quota spent. Subclass of `RateLimitError` |
| `APIError` | any other 4xx/5xx, or an unreadable response |
| `TransportError` | never reached the API: DNS, connection, TLS, timeout (`__cause__` is the httpx error) |
| `ConfigurationError` | bad client options (raised before any request) |

Each carries `.status_code`, `.request_id` and `.body` where available.

```python
from macadress import Client, RateLimitError, MacadressError

try:
    r = mac.lookup(value)
except RateLimitError as exc:
    time.sleep(exc.retry_after or 5)
except MacadressError as exc:
    log.warning("macadress %s: %s (%s)", exc.status_code, exc, exc.request_id)
```

## Development

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"

pytest
mypy
ruff check .
```

The version lives in `src/macadress/_version.py`; keep `CHANGELOG.md` and the
release tag in step with it.

## Links

- API reference: <https://macadress.com/docs>
- Issues: <https://github.com/sapisos/macadress-python/issues>

## License

MIT, see [LICENSE](LICENSE). A product of [ApisOS FZE](https://apisos.com).
