A fast, zeep-free ONVIF-compatible SOAP client for IP cameras (Profile S/T/G/M/A/C).
Project description
onveef
A fast, zeep-free ONVIF client for IP cameras. No runtime WSDL parsing,
near-instant import, sans-IO core, and only two runtime dependencies
(httpx + defusedxml). Talks to ONVIF Profile S / T / G / M / A / C devices —
device management, media & media2, PTZ, imaging, events, analytics, recording,
replay, search, DeviceIO, physical access control, and WS-Discovery.
onveefis a working name — rename freely (see Renaming). This is a client library compatible with ONVIF devices; it is not ONVIF-certified and is not affiliated with or endorsed by the ONVIF trademark holder.
Why another ONVIF library?
Every mainstream Python ONVIF library is built on zeep,
which downloads and parses the ONVIF WSDL/XSD at runtime: slow import, heavy
memory, and a long tail of camera-compatibility bugs (unparsed GetCapabilities
extensions, mangled PullMessages, xsd:any breakage). onveef hand-builds SOAP
envelopes and parses responses with the standard library, so it:
- imports instantly — no WSDL to load, no
lxml/zeepstartup cost; - avoids zeep's ONVIF quirks entirely;
- is sans-IO at the core — request builders (
onveef.envelopes) and response parsers (onveef.parsers) are pure functions over strings/XML, so behaviour is verifiable from recorded fixtures without hardware; - parses XML with
defusedxml(XXE-safe) and escapes every value it emits.
Runtime dependencies: httpx and defusedxml. That's it.
Install
pip install onveef
Requires Python 3.11+.
Quickstart
Just pass the camera's IP, port, and credentials — the per-service endpoints are
discovered automatically on first use (no manual discover_services() dance):
from onveef import OnvifClient
with OnvifClient("192.168.1.64", 80, "admin", "secret", verify_tls=False) as cam:
print(cam.get_device_information())
for profile in cam.get_profiles():
token = profile["token"]
print(token, cam.get_stream_uri(profile_token=token))
print("snapshot:", cam.get_snapshot_uri(profile_token=token))
image, content_type = cam.get_snapshot(profile_token=token)
use_https=True builds an https:// device URL; a full
http(s)://host/onvif/device_service string is also accepted as the first
argument. For full manual control you can still pass a pre-built
endpoint=OnvifEndpoint(...) and credentials=OnvifCredentials(...) (in that
mode auto-discovery stays off and you manage the service map yourself).
Async
AsyncOnvifClient has the same constructor and full method parity with the sync
client (same auth, clock-skew recovery, retries, circuit breaker, and lazy
auto-discovery):
import asyncio
from onveef import AsyncOnvifClient
async def main() -> None:
async with AsyncOnvifClient("192.168.1.64", 80, "admin", "secret", verify_tls=False) as cam:
print(await cam.get_device_information())
for p in await cam.get_profiles():
print(await cam.get_stream_uri(profile_token=p["token"]))
asyncio.run(main())
wsdiscovery.discover_async() provides the same LAN discovery on an asyncio loop.
Typed models (optional)
Parsers return plain dicts; onveef.models wraps the common ones in dataclasses
for real IDE autocomplete and mypy checking:
from onveef import models
info = models.DeviceInformation.from_dict(client.get_device_information())
profile = models.Profile.from_dict(client.get_profiles()[0])
print(info.manufacturer, profile.video_encoder.encoding)
Discover devices on the LAN (WS-Discovery)
from onveef import wsdiscovery
for dev in wsdiscovery.discover(timeout_s=3.0):
print(dev.name, dev.device_service, dev.hardware, dev.scopes)
wsdiscovery.build_probe() / wsdiscovery.parse_probe_matches() are exposed
separately so you can drive the multicast yourself or unit-test against captures.
PTZ
client.ptz_continuous_move(profile_token=token, pan=0.5, tilt=0.0, zoom=0.0)
client.ptz_stop(profile_token=token)
client.ptz_goto_preset(profile_token=token, preset_token="1")
Events (pull-point and push)
# Pull-point (client-polled), optionally scoped to a topic:
sub = client.events_create_pull_point(topic_filter="tns1:RuleEngine//.")
msgs = client.events_pull_messages(subscription_url=sub["subscription_url"])
# Base-notification push subscription (device POSTs to your consumer URL):
sub = client.events_subscribe(consumer_address="http://my-host:9000/onvif-events")
Physical Access Control (Profile A/C)
doors = client.get_door_info_list() # paged; pass start_reference to page
client.unlock_door(token="Door_1")
print(client.get_door_state(token="Door_1"))
print(client.get_access_point_state(token="AP_1"))
client.enable_credential(token="Cred_1", reason="reinstated")
Capabilities gating
caps = client.get_service_capabilities("ptz") # any service
if client.endpoint.has("deviceio"):
client.get_relay_output_options()
Authentication
onveef implements WS-Security UsernameToken PasswordDigest by default and
automatically re-signs with a device-derived clock offset when a request is rejected
for time skew (a common cause of 401s on cameras with wrong clocks). Options:
| Constructor arg | Default | Meaning |
|---|---|---|
verify_tls |
True |
TLS certificate verification. Set False for cameras with self-signed certs. |
http_auth |
"auto" |
HTTP transport auth to try when a device answers 401 with a WWW-Authenticate challenge — "auto" (Digest→Basic), "digest", "basic", or "none". Works alongside WS-Security, for cameras that require HTTP Digest on the SOAP endpoint. |
password_text |
False |
Force WS-Security PasswordText (plaintext) instead of digest. |
password_text_fallback |
True |
On digest 401, retry once with PasswordText (some cheap firmware only accepts plaintext). A warning is logged when it triggers — it is plaintext over the wire on non-HTTPS transports. |
ws_timestamp |
False |
Add a <wsu:Timestamp>/Expires to the security header (required by some strict devices). |
retries |
2 |
Automatic retries (jittered backoff) for transient failures on idempotent (read) operations only. |
breaker_key |
None |
Enable a per-client circuit breaker keyed by this id (e.g. device id). Omit to disable. Tune with breaker_window_s / breaker_threshold / breaker_open_s. |
timeout_s |
5.0 |
Default timeout (connect/read/write/pool). Split with connect_timeout_s / read_timeout_s; long-poll PullMessages automatically extends its read timeout to the pull duration. |
auto_discover |
host-form: True |
Discover per-service endpoints lazily on first use. |
Snapshot fetches use HTTP Digest with a Basic fallback (get_snapshot(...) /
fetch_snapshot_bytes(...), TLS verified by default). The library logs to the
onveef logger — enable logging.getLogger("onveef") at DEBUG/WARNING to see
retries, auth fallbacks, clock resync, and breaker events.
ONVIF coverage
| Service | Coverage | Notes |
|---|---|---|
| Device Management | ✅ full | info, services/capabilities discovery, datetime (+clock-skew), hostname, users, scopes, network, DNS/NTP, log, certs, dot1x |
| Media (Profile S) | ✅ full | profiles, stream/snapshot URI (transport options), video/audio encoder get+set, OSD, metadata, multicast |
| Media2 (Profile T) | ✅ strong | profiles + create/delete, AddConfiguration/RemoveConfiguration, encoder options, masks, sync point |
| PTZ | ✅ strong | continuous/absolute/relative, presets, home, aux, nodes, status, configurations |
| Imaging | ✅ strong | settings/options/status, focus move/stop |
| Events | ✅ strong | pull-point (+topic filter, sync point) and WS-BaseNotification Subscribe push |
| Analytics | ✅ strong | rules & modules CRUD (SimpleItem + ElementItem params) |
| Recording / Replay / Search (Profile G) | ✅ strong | recordings/jobs CRUD, replay URI/config, find sessions |
| DeviceIO | ✅ strong | relays (DeviceIO-aware), relay output options, digital inputs, serial ports |
| Access Control / Door / Credential (Profile A/C) | ✅ strong | access points, areas, doors (+lock/unlock/block/lockdown), credentials |
| WS-Discovery | ✅ full | multicast Probe + ProbeMatch parsing |
| Network | ✅ full | interfaces (IPv4 and IPv6), protocols, gateway, DNS, NTP |
See CHANGELOG.md for what changed per release.
Design notes
- Sans-IO core.
onveef.envelopesandonveef.parsersnever touch the network. All I/O is inonveef.client(synchttpx). This makes the codec fully testable from recorded XML. - Capability gating.
OnvifEndpoint.has()/url()and the client's service resolution raise a cleanOnvifCapabilityMissingErrorinstead of a device fault when a service is not advertised. - DeviceIO aware. Relay and digital-input calls route to the DeviceIO service when advertised, falling back to the legacy device service.
- Pluggable resilience. Optional in-memory circuit breaker; bounded (8 MiB)
streamed responses; content-type negotiation (
application/soap+xml→text/xml).
Publishing
Build and release instructions (build, TestPyPI rehearsal, PyPI upload, GitHub
Actions trusted publishing, renaming) live in PUBLISHING.md. TL;DR:
uv sync --extra dev
uv run ruff check src tests && uv run mypy && uv run pytest -q
uv build
uv run twine check dist/*
uv run twine upload --repository testpypi dist/* # rehearse
uv run twine upload dist/* # release
Roadmap
Delivered in 0.3: async client, typed models, WS-Discovery (sync + async), device/imaging/PTZ extras, and a recorded-fixture vendor harness. Next:
- Grow the vendor fixture matrix with real captures (Hikvision / Dahua / Axis / Bosch / Reolink / Uniview / Amcrest).
- Full async method parity with the sync client (the generic
await call()already reaches every operation today). - Access Control write ops (create/modify credentials, schedules, access profiles).
Renaming
onveef is a placeholder. To rename to <newname>:
git grep -l onveef | xargs sed -i 's/onveef/<newname>/g'
git mv src/onveef src/<newname>
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file onveef-0.4.1.tar.gz.
File metadata
- Download URL: onveef-0.4.1.tar.gz
- Upload date:
- Size: 166.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a727d548a2ae73361141667fbc062daa270728a2255bd204febaff45ea6fb83e
|
|
| MD5 |
f076199b800e230b9f67d4d29a16ab1e
|
|
| BLAKE2b-256 |
40197d2953dada543c4a4448f2002ded6a532fba9be1e96688d8de776034046c
|
Provenance
The following attestation bundles were made for onveef-0.4.1.tar.gz:
Publisher:
release.yml on Akshat-Pandey16/onveef
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onveef-0.4.1.tar.gz -
Subject digest:
a727d548a2ae73361141667fbc062daa270728a2255bd204febaff45ea6fb83e - Sigstore transparency entry: 2048363072
- Sigstore integration time:
-
Permalink:
Akshat-Pandey16/onveef@9839f0c5d5751e4c1d3ca05748e3b8e5529ede24 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/Akshat-Pandey16
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9839f0c5d5751e4c1d3ca05748e3b8e5529ede24 -
Trigger Event:
push
-
Statement type:
File details
Details for the file onveef-0.4.1-py3-none-any.whl.
File metadata
- Download URL: onveef-0.4.1-py3-none-any.whl
- Upload date:
- Size: 80.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c2f19d7b3da42dfa30a5d87f8194fea2f7033f9f5bdf9bfb92d5a946da4eb65
|
|
| MD5 |
0772962fe2936bac645a7887e4a429b1
|
|
| BLAKE2b-256 |
8d5b1691581860d393f4718a1e9c9145da6a8e7a879bc8999bb6d603d0682b74
|
Provenance
The following attestation bundles were made for onveef-0.4.1-py3-none-any.whl:
Publisher:
release.yml on Akshat-Pandey16/onveef
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onveef-0.4.1-py3-none-any.whl -
Subject digest:
7c2f19d7b3da42dfa30a5d87f8194fea2f7033f9f5bdf9bfb92d5a946da4eb65 - Sigstore transparency entry: 2048363084
- Sigstore integration time:
-
Permalink:
Akshat-Pandey16/onveef@9839f0c5d5751e4c1d3ca05748e3b8e5529ede24 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/Akshat-Pandey16
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9839f0c5d5751e4c1d3ca05748e3b8e5529ede24 -
Trigger Event:
push
-
Statement type: