onvif-python 0.0.7__tar.gz → 0.0.8__tar.gz
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.
- {onvif_python-0.0.7/onvif_python.egg-info → onvif_python-0.0.8}/PKG-INFO +41 -27
- {onvif_python-0.0.7 → onvif_python-0.0.8}/README.md +38 -24
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/__init__.py +2 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/__init__.py +2 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/xml_capture.py +43 -8
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/zeep.py +175 -20
- {onvif_python-0.0.7 → onvif_python-0.0.8/onvif_python.egg-info}/PKG-INFO +41 -27
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/requires.txt +2 -2
- {onvif_python-0.0.7 → onvif_python-0.0.8}/pyproject.toml +13 -4
- {onvif_python-0.0.7 → onvif_python-0.0.8}/LICENSE.md +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/MANIFEST.in +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/__init__.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/client.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/operator.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/accesscontrol.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/accessrules.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/actionengine.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analytics/analytics.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analytics/ruleengine.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analyticsdevice.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/appmgmt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/authenticationbehavior.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/credential.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/deviceio.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/devicemgmt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/display.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/doorcontrol.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/events.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/notification.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/pullpoint.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/subscription.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/imaging.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/media.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/media2.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/provisioning.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/ptz.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/receiver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/recording.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/replay.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/schedule.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/search.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/advancedsecurity.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/authorizationserver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/dot1x.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/jwt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/keystore.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/mediasigning.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/tlsserver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/thermal.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/uplink.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/error_handlers.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/exceptions.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/wsdl.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display/display.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/receiver.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/recording.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/replay.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/common.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/search.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/SOURCES.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/dependency_links.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/top_level.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/setup.cfg +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/setup.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_client.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_core.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_exceptions.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_wsdl_map.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices
|
|
5
5
|
Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
|
|
6
6
|
Maintainer: Kaburagi
|
|
@@ -31,8 +31,8 @@ Classifier: Natural Language :: Indonesian
|
|
|
31
31
|
Requires-Python: >=3.9
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
License-File: LICENSE.md
|
|
34
|
-
Requires-Dist: zeep>=4.
|
|
35
|
-
Requires-Dist: requests>=2.
|
|
34
|
+
Requires-Dist: zeep>=4.3.0
|
|
35
|
+
Requires-Dist: requests>=2.32.0
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
38
|
Requires-Dist: black; extra == "dev"
|
|
@@ -43,14 +43,11 @@ Dynamic: license-file
|
|
|
43
43
|
|
|
44
44
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
45
45
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
46
|
-
[](https://github.com/nirsimetri/onvif-python/releases)
|
|
47
47
|
<br>
|
|
48
|
-
[](https://pypi.org/project/onvif-python/)
|
|
49
49
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
50
50
|
|
|
51
|
-
Are you having trouble finding a Python ONVIF library that supports your device?
|
|
52
|
-
Are you confused about the compatibility of the various ONVIF versions, which are updated every six months?
|
|
53
|
-
|
|
54
51
|
**This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
|
|
55
52
|
|
|
56
53
|
**[ONVIF](https://www.onvif.org) (Open Network Video Interface Forum)** is a global standard for the interface of IP-based physical security products, including network cameras, video recorders, and related systems.
|
|
@@ -154,7 +151,9 @@ Explore more advanced usage and service-specific operations in the [`examples/`]
|
|
|
154
151
|
|
|
155
152
|
The `ONVIFClient` class provides various configuration options to customize the connection behavior, caching strategy, security settings, and debugging capabilities. Below is a detailed description of all available parameters:
|
|
156
153
|
|
|
157
|
-
|
|
154
|
+
|
|
155
|
+
<details>
|
|
156
|
+
<summary><b>Basic Parameters</b></summary>
|
|
158
157
|
|
|
159
158
|
| Parameter | Type | Required | Default | Description |
|
|
160
159
|
|-----------|------|----------|---------|-------------|
|
|
@@ -163,7 +162,10 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
163
162
|
| `username` | `str` | ✅ Yes | - | Username for device authentication (use digest authentication) |
|
|
164
163
|
| `password` | `str` | ✅ Yes | - | Password for device authentication |
|
|
165
164
|
|
|
166
|
-
|
|
165
|
+
</details>
|
|
166
|
+
|
|
167
|
+
<details>
|
|
168
|
+
<summary><b>Connection Parameters</b></summary>
|
|
167
169
|
|
|
168
170
|
| Parameter | Type | Required | Default | Description |
|
|
169
171
|
|-----------|------|----------|---------|-------------|
|
|
@@ -171,33 +173,45 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
171
173
|
| `use_https` | `bool` | ❌ No | `False` | Use HTTPS instead of HTTP for secure communication |
|
|
172
174
|
| `verify_ssl` | `bool` | ❌ No | `True` | Verify SSL certificates when using HTTPS (set to `False` for self-signed certificates) |
|
|
173
175
|
|
|
174
|
-
|
|
176
|
+
</details>
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary><b>Caching Parameters</b></summary>
|
|
175
180
|
|
|
176
181
|
| Parameter | Type | Required | Default | Description |
|
|
177
182
|
|-----------|------|----------|---------|-------------|
|
|
178
183
|
| `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
|
|
179
184
|
|
|
180
|
-
|
|
185
|
+
</details>
|
|
186
|
+
|
|
187
|
+
<details>
|
|
188
|
+
<summary><b>Feature Parameters</b></summary>
|
|
181
189
|
|
|
182
190
|
| Parameter | Type | Required | Default | Description |
|
|
183
191
|
|-----------|------|----------|---------|-------------|
|
|
184
|
-
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better xsd:any field parsing and automatic flattening |
|
|
192
|
+
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better `xsd:any` field parsing and automatic flattening |
|
|
185
193
|
| `capture_xml` | `bool` | ❌ No | `False` | Enable XML capture plugin for debugging SOAP requests/responses |
|
|
186
194
|
|
|
187
|
-
|
|
195
|
+
</details>
|
|
196
|
+
|
|
197
|
+
<details>
|
|
198
|
+
<summary><b>Cache Modes</b></summary>
|
|
188
199
|
|
|
189
200
|
The library provides four caching strategies via the `CacheMode` enum:
|
|
190
201
|
|
|
191
202
|
| Mode | Description | Best For | Startup Speed | Disk Usage | Memory Usage |
|
|
192
203
|
|------|-------------|----------|---------------|------------|--------------|
|
|
193
|
-
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps |
|
|
194
|
-
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools |
|
|
195
|
-
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos |
|
|
196
|
-
| `CacheMode.NONE` | No caching | Testing, debugging |
|
|
204
|
+
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps | Fast | High | High |
|
|
205
|
+
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | Medium | Medium | Low |
|
|
206
|
+
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | Medium | None | Medium |
|
|
207
|
+
| `CacheMode.NONE` | No caching | Testing, debugging | Slow | None | Low |
|
|
197
208
|
|
|
198
209
|
**Recommendation:** Use `CacheMode.ALL` (default) for production applications to maximize performance.
|
|
199
210
|
|
|
200
|
-
|
|
211
|
+
</details>
|
|
212
|
+
|
|
213
|
+
<details>
|
|
214
|
+
<summary><b>Usage Examples</b></summary>
|
|
201
215
|
|
|
202
216
|
**Basic Connection:**
|
|
203
217
|
```python
|
|
@@ -218,8 +232,7 @@ client = ONVIFClient(
|
|
|
218
232
|
"admin",
|
|
219
233
|
"password",
|
|
220
234
|
timeout=30,
|
|
221
|
-
use_https=True
|
|
222
|
-
verify_ssl=False # For self-signed certificates
|
|
235
|
+
use_https=True
|
|
223
236
|
)
|
|
224
237
|
```
|
|
225
238
|
|
|
@@ -237,7 +250,7 @@ client = ONVIFClient(
|
|
|
237
250
|
)
|
|
238
251
|
```
|
|
239
252
|
|
|
240
|
-
**No Caching (Testing):**
|
|
253
|
+
**No Caching and No Zeep Patching (Testing):**
|
|
241
254
|
```python
|
|
242
255
|
from onvif import ONVIFClient, CacheMode
|
|
243
256
|
|
|
@@ -296,7 +309,6 @@ if client.xml_plugin:
|
|
|
296
309
|
client.xml_plugin.clear_history()
|
|
297
310
|
```
|
|
298
311
|
|
|
299
|
-
> [!NOTE]
|
|
300
312
|
> **XML Capture Plugin Methods:**
|
|
301
313
|
> - `last_sent_xml` - Get the last SOAP request XML
|
|
302
314
|
> - `last_received_xml` - Get the last SOAP response XML
|
|
@@ -308,8 +320,11 @@ if client.xml_plugin:
|
|
|
308
320
|
> - `save_to_file(request_file, response_file)` - Save XML to files
|
|
309
321
|
> - `clear_history()` - Clear captured history
|
|
310
322
|
|
|
323
|
+
</details>
|
|
324
|
+
|
|
325
|
+
<details>
|
|
326
|
+
<summary><b>Production Configuration</b></summary>
|
|
311
327
|
|
|
312
|
-
**Production Configuration:**
|
|
313
328
|
```python
|
|
314
329
|
from onvif import ONVIFClient, CacheMode
|
|
315
330
|
|
|
@@ -320,13 +335,14 @@ client = ONVIFClient(
|
|
|
320
335
|
username="admin",
|
|
321
336
|
password="secure_password",
|
|
322
337
|
timeout=15,
|
|
323
|
-
cache=CacheMode.ALL, # Maximum performance
|
|
338
|
+
cache=CacheMode.ALL, # Maximum performance (default)
|
|
324
339
|
use_https=True, # Secure communication
|
|
325
340
|
verify_ssl=True, # Verify certificates (default)
|
|
326
341
|
apply_patch=True, # Enhanced parsing (default)
|
|
327
342
|
capture_xml=False # Disable debug mode (default)
|
|
328
343
|
)
|
|
329
344
|
```
|
|
345
|
+
</details>
|
|
330
346
|
|
|
331
347
|
### Notes
|
|
332
348
|
|
|
@@ -598,8 +614,6 @@ This project is an **independent open-source implementation** of the [ONVIF](htt
|
|
|
598
614
|
|
|
599
615
|
If you require certified ONVIF-compliant devices or clients, please refer to the official [ONVIF conformant product list](https://www.onvif.org/conformant-products/). For authoritative reference and the latest official ONVIF specifications, please consult the [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/).
|
|
600
616
|
|
|
601
|
-
Use of this library is at your own risk. The authors and contributors assume no liability for any damages, direct or indirect, arising from its use.
|
|
602
|
-
|
|
603
617
|
## License
|
|
604
618
|
|
|
605
619
|
This project is licensed under the MIT License. See [LICENSE](./LICENSE.md) for details.
|
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
4
4
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
5
|
-
[](https://github.com/nirsimetri/onvif-python/releases)
|
|
6
6
|
<br>
|
|
7
|
-
[](https://pypi.org/project/onvif-python/)
|
|
8
8
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
9
9
|
|
|
10
|
-
Are you having trouble finding a Python ONVIF library that supports your device?
|
|
11
|
-
Are you confused about the compatibility of the various ONVIF versions, which are updated every six months?
|
|
12
|
-
|
|
13
10
|
**This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
|
|
14
11
|
|
|
15
12
|
**[ONVIF](https://www.onvif.org) (Open Network Video Interface Forum)** is a global standard for the interface of IP-based physical security products, including network cameras, video recorders, and related systems.
|
|
@@ -113,7 +110,9 @@ Explore more advanced usage and service-specific operations in the [`examples/`]
|
|
|
113
110
|
|
|
114
111
|
The `ONVIFClient` class provides various configuration options to customize the connection behavior, caching strategy, security settings, and debugging capabilities. Below is a detailed description of all available parameters:
|
|
115
112
|
|
|
116
|
-
|
|
113
|
+
|
|
114
|
+
<details>
|
|
115
|
+
<summary><b>Basic Parameters</b></summary>
|
|
117
116
|
|
|
118
117
|
| Parameter | Type | Required | Default | Description |
|
|
119
118
|
|-----------|------|----------|---------|-------------|
|
|
@@ -122,7 +121,10 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
122
121
|
| `username` | `str` | ✅ Yes | - | Username for device authentication (use digest authentication) |
|
|
123
122
|
| `password` | `str` | ✅ Yes | - | Password for device authentication |
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary><b>Connection Parameters</b></summary>
|
|
126
128
|
|
|
127
129
|
| Parameter | Type | Required | Default | Description |
|
|
128
130
|
|-----------|------|----------|---------|-------------|
|
|
@@ -130,33 +132,45 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
130
132
|
| `use_https` | `bool` | ❌ No | `False` | Use HTTPS instead of HTTP for secure communication |
|
|
131
133
|
| `verify_ssl` | `bool` | ❌ No | `True` | Verify SSL certificates when using HTTPS (set to `False` for self-signed certificates) |
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary><b>Caching Parameters</b></summary>
|
|
134
139
|
|
|
135
140
|
| Parameter | Type | Required | Default | Description |
|
|
136
141
|
|-----------|------|----------|---------|-------------|
|
|
137
142
|
| `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
<details>
|
|
147
|
+
<summary><b>Feature Parameters</b></summary>
|
|
140
148
|
|
|
141
149
|
| Parameter | Type | Required | Default | Description |
|
|
142
150
|
|-----------|------|----------|---------|-------------|
|
|
143
|
-
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better xsd:any field parsing and automatic flattening |
|
|
151
|
+
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better `xsd:any` field parsing and automatic flattening |
|
|
144
152
|
| `capture_xml` | `bool` | ❌ No | `False` | Enable XML capture plugin for debugging SOAP requests/responses |
|
|
145
153
|
|
|
146
|
-
|
|
154
|
+
</details>
|
|
155
|
+
|
|
156
|
+
<details>
|
|
157
|
+
<summary><b>Cache Modes</b></summary>
|
|
147
158
|
|
|
148
159
|
The library provides four caching strategies via the `CacheMode` enum:
|
|
149
160
|
|
|
150
161
|
| Mode | Description | Best For | Startup Speed | Disk Usage | Memory Usage |
|
|
151
162
|
|------|-------------|----------|---------------|------------|--------------|
|
|
152
|
-
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps |
|
|
153
|
-
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools |
|
|
154
|
-
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos |
|
|
155
|
-
| `CacheMode.NONE` | No caching | Testing, debugging |
|
|
163
|
+
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps | Fast | High | High |
|
|
164
|
+
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | Medium | Medium | Low |
|
|
165
|
+
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | Medium | None | Medium |
|
|
166
|
+
| `CacheMode.NONE` | No caching | Testing, debugging | Slow | None | Low |
|
|
156
167
|
|
|
157
168
|
**Recommendation:** Use `CacheMode.ALL` (default) for production applications to maximize performance.
|
|
158
169
|
|
|
159
|
-
|
|
170
|
+
</details>
|
|
171
|
+
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>Usage Examples</b></summary>
|
|
160
174
|
|
|
161
175
|
**Basic Connection:**
|
|
162
176
|
```python
|
|
@@ -177,8 +191,7 @@ client = ONVIFClient(
|
|
|
177
191
|
"admin",
|
|
178
192
|
"password",
|
|
179
193
|
timeout=30,
|
|
180
|
-
use_https=True
|
|
181
|
-
verify_ssl=False # For self-signed certificates
|
|
194
|
+
use_https=True
|
|
182
195
|
)
|
|
183
196
|
```
|
|
184
197
|
|
|
@@ -196,7 +209,7 @@ client = ONVIFClient(
|
|
|
196
209
|
)
|
|
197
210
|
```
|
|
198
211
|
|
|
199
|
-
**No Caching (Testing):**
|
|
212
|
+
**No Caching and No Zeep Patching (Testing):**
|
|
200
213
|
```python
|
|
201
214
|
from onvif import ONVIFClient, CacheMode
|
|
202
215
|
|
|
@@ -255,7 +268,6 @@ if client.xml_plugin:
|
|
|
255
268
|
client.xml_plugin.clear_history()
|
|
256
269
|
```
|
|
257
270
|
|
|
258
|
-
> [!NOTE]
|
|
259
271
|
> **XML Capture Plugin Methods:**
|
|
260
272
|
> - `last_sent_xml` - Get the last SOAP request XML
|
|
261
273
|
> - `last_received_xml` - Get the last SOAP response XML
|
|
@@ -267,8 +279,11 @@ if client.xml_plugin:
|
|
|
267
279
|
> - `save_to_file(request_file, response_file)` - Save XML to files
|
|
268
280
|
> - `clear_history()` - Clear captured history
|
|
269
281
|
|
|
282
|
+
</details>
|
|
283
|
+
|
|
284
|
+
<details>
|
|
285
|
+
<summary><b>Production Configuration</b></summary>
|
|
270
286
|
|
|
271
|
-
**Production Configuration:**
|
|
272
287
|
```python
|
|
273
288
|
from onvif import ONVIFClient, CacheMode
|
|
274
289
|
|
|
@@ -279,13 +294,14 @@ client = ONVIFClient(
|
|
|
279
294
|
username="admin",
|
|
280
295
|
password="secure_password",
|
|
281
296
|
timeout=15,
|
|
282
|
-
cache=CacheMode.ALL, # Maximum performance
|
|
297
|
+
cache=CacheMode.ALL, # Maximum performance (default)
|
|
283
298
|
use_https=True, # Secure communication
|
|
284
299
|
verify_ssl=True, # Verify certificates (default)
|
|
285
300
|
apply_patch=True, # Enhanced parsing (default)
|
|
286
301
|
capture_xml=False # Disable debug mode (default)
|
|
287
302
|
)
|
|
288
303
|
```
|
|
304
|
+
</details>
|
|
289
305
|
|
|
290
306
|
### Notes
|
|
291
307
|
|
|
@@ -557,8 +573,6 @@ This project is an **independent open-source implementation** of the [ONVIF](htt
|
|
|
557
573
|
|
|
558
574
|
If you require certified ONVIF-compliant devices or clients, please refer to the official [ONVIF conformant product list](https://www.onvif.org/conformant-products/). For authoritative reference and the latest official ONVIF specifications, please consult the [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/).
|
|
559
575
|
|
|
560
|
-
Use of this library is at your own risk. The authors and contributors assume no liability for any damages, direct or indirect, arising from its use.
|
|
561
|
-
|
|
562
576
|
## License
|
|
563
577
|
|
|
564
578
|
This project is licensed under the MIT License. See [LICENSE](./LICENSE.md) for details.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from zeep import Plugin
|
|
4
4
|
from lxml import etree
|
|
5
|
+
from xml.dom import minidom
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class XMLCapturePlugin(Plugin):
|
|
@@ -35,12 +36,43 @@ class XMLCapturePlugin(Plugin):
|
|
|
35
36
|
self.last_operation = None
|
|
36
37
|
self.history = [] # Store all requests/responses
|
|
37
38
|
|
|
39
|
+
def _format_xml(self, element):
|
|
40
|
+
"""
|
|
41
|
+
Format XML element with proper indentation using minidom.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
element: lxml Element to format
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
str: Pretty-printed XML string
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Convert lxml element to string
|
|
51
|
+
xml_string = etree.tostring(element, encoding="unicode")
|
|
52
|
+
# Parse with minidom and pretty print
|
|
53
|
+
dom = minidom.parseString(xml_string)
|
|
54
|
+
# Use toprettyxml with proper indentation
|
|
55
|
+
pretty_xml = dom.toprettyxml(indent=" ", encoding=None)
|
|
56
|
+
# Remove extra blank lines and XML declaration
|
|
57
|
+
lines = [line for line in pretty_xml.split("\n") if line.strip()]
|
|
58
|
+
# Remove XML declaration line if present
|
|
59
|
+
if lines and lines[0].startswith("<?xml"):
|
|
60
|
+
lines = lines[1:]
|
|
61
|
+
return "\n".join(lines)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
# Fallback to lxml if minidom fails
|
|
64
|
+
return etree.tostring(element, pretty_print=True, encoding="unicode")
|
|
65
|
+
|
|
38
66
|
def egress(self, envelope, http_headers, operation, binding_options):
|
|
39
67
|
"""Called before sending the SOAP request"""
|
|
40
|
-
# Serialize XML
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
# Serialize XML with proper pretty printing
|
|
69
|
+
if self.pretty_print:
|
|
70
|
+
self.last_sent_xml = self._format_xml(envelope)
|
|
71
|
+
else:
|
|
72
|
+
self.last_sent_xml = etree.tostring(
|
|
73
|
+
envelope, pretty_print=False, encoding="unicode"
|
|
74
|
+
)
|
|
75
|
+
|
|
44
76
|
self.last_operation = operation.name
|
|
45
77
|
|
|
46
78
|
# Store in history
|
|
@@ -57,10 +89,13 @@ class XMLCapturePlugin(Plugin):
|
|
|
57
89
|
|
|
58
90
|
def ingress(self, envelope, http_headers, operation):
|
|
59
91
|
"""Called after receiving the SOAP response"""
|
|
60
|
-
# Serialize XML
|
|
61
|
-
self.
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
# Serialize XML with proper pretty printing
|
|
93
|
+
if self.pretty_print:
|
|
94
|
+
self.last_received_xml = self._format_xml(envelope)
|
|
95
|
+
else:
|
|
96
|
+
self.last_received_xml = etree.tostring(
|
|
97
|
+
envelope, pretty_print=False, encoding="unicode"
|
|
98
|
+
)
|
|
64
99
|
|
|
65
100
|
# Store in history
|
|
66
101
|
self.history.append(
|
|
@@ -63,6 +63,93 @@ class ZeepPatcher:
|
|
|
63
63
|
except ValueError:
|
|
64
64
|
return val
|
|
65
65
|
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _parse_element_recursive(element):
|
|
68
|
+
"""
|
|
69
|
+
Recursively parse an XML element and its children into a dictionary.
|
|
70
|
+
|
|
71
|
+
Handles:
|
|
72
|
+
- Elements with attributes only (converted to dict)
|
|
73
|
+
- Elements with children only (recursively parsed)
|
|
74
|
+
- Elements with BOTH attributes AND children (merged into single dict)
|
|
75
|
+
- Elements with text content only (type-converted)
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
element: lxml Element to parse
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict, parsed value, or None
|
|
82
|
+
"""
|
|
83
|
+
result = {}
|
|
84
|
+
|
|
85
|
+
for child in element:
|
|
86
|
+
child_qname = QName(child.tag)
|
|
87
|
+
child_name = child_qname.localname
|
|
88
|
+
child_has_attrib = bool(child.attrib)
|
|
89
|
+
child_has_children = bool(list(child))
|
|
90
|
+
|
|
91
|
+
if child_has_attrib and child_has_children:
|
|
92
|
+
# Element has BOTH attributes AND children - merge them
|
|
93
|
+
child_dict = {
|
|
94
|
+
k: ZeepPatcher.parse_text_value(v) for k, v in child.attrib.items()
|
|
95
|
+
}
|
|
96
|
+
# Recursively parse children and merge into the same dict
|
|
97
|
+
nested = ZeepPatcher._parse_element_recursive(child)
|
|
98
|
+
if nested:
|
|
99
|
+
child_dict.update(nested)
|
|
100
|
+
result[child_name] = child_dict
|
|
101
|
+
elif child_has_attrib:
|
|
102
|
+
# Element with attributes only
|
|
103
|
+
result[child_name] = {
|
|
104
|
+
k: ZeepPatcher.parse_text_value(v) for k, v in child.attrib.items()
|
|
105
|
+
}
|
|
106
|
+
elif child_has_children:
|
|
107
|
+
# Element has nested children only
|
|
108
|
+
result[child_name] = ZeepPatcher._parse_element_recursive(child)
|
|
109
|
+
else:
|
|
110
|
+
# Element only has text content
|
|
111
|
+
result[child_name] = ZeepPatcher.parse_text_value(child.text)
|
|
112
|
+
|
|
113
|
+
return result if result else None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _zeep_object_to_dict(obj):
|
|
117
|
+
"""
|
|
118
|
+
Convert zeep object to dictionary, including manually added attributes.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
obj: Zeep object or primitive value
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Dict or primitive value
|
|
125
|
+
"""
|
|
126
|
+
if obj is None:
|
|
127
|
+
return None
|
|
128
|
+
if isinstance(obj, (str, int, float, bool)):
|
|
129
|
+
return obj
|
|
130
|
+
if isinstance(obj, list):
|
|
131
|
+
return [ZeepPatcher._zeep_object_to_dict(item) for item in obj]
|
|
132
|
+
if isinstance(obj, dict):
|
|
133
|
+
return {k: ZeepPatcher._zeep_object_to_dict(v) for k, v in obj.items()}
|
|
134
|
+
|
|
135
|
+
# Handle zeep objects with __values__
|
|
136
|
+
if hasattr(obj, "__values__"):
|
|
137
|
+
result = {}
|
|
138
|
+
for key, val in obj.__values__.items():
|
|
139
|
+
if not key.startswith("_"): # Skip private fields like _value_1
|
|
140
|
+
result[key] = ZeepPatcher._zeep_object_to_dict(val)
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
# Handle objects with __dict__
|
|
144
|
+
if hasattr(obj, "__dict__"):
|
|
145
|
+
result = {}
|
|
146
|
+
for key, val in obj.__dict__.items():
|
|
147
|
+
if not key.startswith("_"):
|
|
148
|
+
result[key] = ZeepPatcher._zeep_object_to_dict(val)
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
return obj
|
|
152
|
+
|
|
66
153
|
@staticmethod
|
|
67
154
|
def flatten_xsd_any_fields(obj, _visited=None):
|
|
68
155
|
"""
|
|
@@ -147,10 +234,24 @@ class ZeepPatcher:
|
|
|
147
234
|
if should_flatten:
|
|
148
235
|
# This is truly a wrapper - flatten by copying fields to parent
|
|
149
236
|
# But still set the tag_name field to the inner_content
|
|
237
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
238
|
+
if hasattr(inner_content, "__values__") or hasattr(
|
|
239
|
+
inner_content, "__dict__"
|
|
240
|
+
):
|
|
241
|
+
inner_content = ZeepPatcher._zeep_object_to_dict(
|
|
242
|
+
inner_content
|
|
243
|
+
)
|
|
150
244
|
values[tag_name] = inner_content
|
|
151
245
|
# Don't copy fields up to parent - keep them in the structured object
|
|
152
246
|
else:
|
|
153
247
|
# Not a wrapper - just set the field directly
|
|
248
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
249
|
+
if hasattr(inner_content, "__values__") or hasattr(
|
|
250
|
+
inner_content, "__dict__"
|
|
251
|
+
):
|
|
252
|
+
inner_content = ZeepPatcher._zeep_object_to_dict(
|
|
253
|
+
inner_content
|
|
254
|
+
)
|
|
154
255
|
if tag_name in values and values[tag_name] is None:
|
|
155
256
|
values[tag_name] = inner_content
|
|
156
257
|
elif tag_name not in values:
|
|
@@ -161,6 +262,10 @@ class ZeepPatcher:
|
|
|
161
262
|
if key.startswith("_"):
|
|
162
263
|
continue
|
|
163
264
|
|
|
265
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
266
|
+
if hasattr(val, "__values__") or hasattr(val, "__dict__"):
|
|
267
|
+
val = ZeepPatcher._zeep_object_to_dict(val)
|
|
268
|
+
|
|
164
269
|
if key in values and values[key] is None:
|
|
165
270
|
values[key] = val
|
|
166
271
|
elif key not in values:
|
|
@@ -207,9 +312,23 @@ class ZeepPatcher:
|
|
|
207
312
|
|
|
208
313
|
if should_flatten:
|
|
209
314
|
# Set the field to the structured content
|
|
315
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
316
|
+
if hasattr(inner_content, "__values__") or hasattr(
|
|
317
|
+
inner_content, "__dict__"
|
|
318
|
+
):
|
|
319
|
+
inner_content = ZeepPatcher._zeep_object_to_dict(
|
|
320
|
+
inner_content
|
|
321
|
+
)
|
|
210
322
|
setattr(obj, tag_name, inner_content)
|
|
211
323
|
else:
|
|
212
324
|
# Not a wrapper - just set the field directly
|
|
325
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
326
|
+
if hasattr(inner_content, "__values__") or hasattr(
|
|
327
|
+
inner_content, "__dict__"
|
|
328
|
+
):
|
|
329
|
+
inner_content = ZeepPatcher._zeep_object_to_dict(
|
|
330
|
+
inner_content
|
|
331
|
+
)
|
|
213
332
|
if (
|
|
214
333
|
hasattr(obj, tag_name)
|
|
215
334
|
and getattr(obj, tag_name) is None
|
|
@@ -223,6 +342,10 @@ class ZeepPatcher:
|
|
|
223
342
|
if key.startswith("_"):
|
|
224
343
|
continue
|
|
225
344
|
|
|
345
|
+
# Convert zeep object to dict to preserve manually added attributes
|
|
346
|
+
if hasattr(val, "__values__") or hasattr(val, "__dict__"):
|
|
347
|
+
val = ZeepPatcher._zeep_object_to_dict(val)
|
|
348
|
+
|
|
226
349
|
if hasattr(obj, key) and getattr(obj, key) is None:
|
|
227
350
|
setattr(obj, key, val)
|
|
228
351
|
elif not hasattr(obj, key):
|
|
@@ -293,10 +416,42 @@ class ZeepPatcher:
|
|
|
293
416
|
|
|
294
417
|
tag_name = QName(xmlelement.tag).localname
|
|
295
418
|
children = list(xmlelement)
|
|
419
|
+
|
|
296
420
|
if children and schema:
|
|
297
421
|
child_result = {}
|
|
422
|
+
|
|
423
|
+
# If xmlelement itself has attributes, add them first
|
|
424
|
+
if xmlelement.attrib:
|
|
425
|
+
child_result.update(
|
|
426
|
+
{
|
|
427
|
+
k: ZeepPatcher.parse_text_value(v)
|
|
428
|
+
for k, v in xmlelement.attrib.items()
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
|
|
298
432
|
for child in children:
|
|
299
433
|
child_qname = QName(child.tag)
|
|
434
|
+
child_localname = child_qname.localname
|
|
435
|
+
|
|
436
|
+
# If child has attributes, parse manually to preserve them
|
|
437
|
+
# Schema parsing often loses attributes not in schema definition
|
|
438
|
+
if child.attrib:
|
|
439
|
+
parsed = {}
|
|
440
|
+
# Add attributes first
|
|
441
|
+
parsed.update(
|
|
442
|
+
{
|
|
443
|
+
k: ZeepPatcher.parse_text_value(v)
|
|
444
|
+
for k, v in child.attrib.items()
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
# Then add nested children
|
|
448
|
+
nested = ZeepPatcher._parse_element_recursive(child)
|
|
449
|
+
if nested:
|
|
450
|
+
parsed.update(nested)
|
|
451
|
+
child_result[child_localname] = parsed
|
|
452
|
+
continue
|
|
453
|
+
|
|
454
|
+
# No attributes - try schema parsing first
|
|
300
455
|
try:
|
|
301
456
|
xsd_el = schema.get_element(child_qname)
|
|
302
457
|
val = xsd_el.parse_xmlelement(
|
|
@@ -305,28 +460,28 @@ class ZeepPatcher:
|
|
|
305
460
|
child_result[child_qname.localname] = val
|
|
306
461
|
except Exception:
|
|
307
462
|
# If schema lookup fails, try to parse manually
|
|
308
|
-
|
|
463
|
+
parsed = {}
|
|
464
|
+
|
|
465
|
+
# Parse child's own attributes first (if any)
|
|
309
466
|
if child.attrib:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
child_result[child_qname.localname] = attr_dict
|
|
316
|
-
elif list(child):
|
|
317
|
-
# Has nested children
|
|
318
|
-
nested = {
|
|
319
|
-
QName(sub.tag).localname: ZeepPatcher.parse_text_value(
|
|
320
|
-
sub.text
|
|
321
|
-
)
|
|
322
|
-
for sub in child
|
|
323
|
-
}
|
|
324
|
-
child_result[child_qname.localname] = nested
|
|
325
|
-
else:
|
|
326
|
-
# Only has text content
|
|
327
|
-
child_result[child_qname.localname] = (
|
|
328
|
-
ZeepPatcher.parse_text_value(child.text)
|
|
467
|
+
parsed.update(
|
|
468
|
+
{
|
|
469
|
+
k: ZeepPatcher.parse_text_value(v)
|
|
470
|
+
for k, v in child.attrib.items()
|
|
471
|
+
}
|
|
329
472
|
)
|
|
473
|
+
|
|
474
|
+
# Parse child's nested children (if any)
|
|
475
|
+
nested = ZeepPatcher._parse_element_recursive(child)
|
|
476
|
+
if nested:
|
|
477
|
+
parsed.update(nested)
|
|
478
|
+
|
|
479
|
+
# If no attributes and no children, try text content
|
|
480
|
+
if not parsed:
|
|
481
|
+
parsed = ZeepPatcher.parse_text_value(child.text)
|
|
482
|
+
|
|
483
|
+
child_result[child_qname.localname] = parsed
|
|
484
|
+
|
|
330
485
|
parsed_result[tag_name] = child_result
|
|
331
486
|
else:
|
|
332
487
|
parsed_result[tag_name] = ZeepPatcher.parse_text_value(xmlelement.text)
|