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.
Files changed (120) hide show
  1. {onvif_python-0.0.7/onvif_python.egg-info → onvif_python-0.0.8}/PKG-INFO +41 -27
  2. {onvif_python-0.0.7 → onvif_python-0.0.8}/README.md +38 -24
  3. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/__init__.py +2 -0
  4. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/__init__.py +2 -0
  5. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/xml_capture.py +43 -8
  6. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/zeep.py +175 -20
  7. {onvif_python-0.0.7 → onvif_python-0.0.8/onvif_python.egg-info}/PKG-INFO +41 -27
  8. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/requires.txt +2 -2
  9. {onvif_python-0.0.7 → onvif_python-0.0.8}/pyproject.toml +13 -4
  10. {onvif_python-0.0.7 → onvif_python-0.0.8}/LICENSE.md +0 -0
  11. {onvif_python-0.0.7 → onvif_python-0.0.8}/MANIFEST.in +0 -0
  12. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/__init__.py +0 -0
  13. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/client.py +0 -0
  14. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/operator.py +0 -0
  15. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/accesscontrol.py +0 -0
  16. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/accessrules.py +0 -0
  17. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/actionengine.py +0 -0
  18. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analytics/analytics.py +0 -0
  19. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analytics/ruleengine.py +0 -0
  20. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/analyticsdevice.py +0 -0
  21. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/appmgmt.py +0 -0
  22. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/authenticationbehavior.py +0 -0
  23. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/credential.py +0 -0
  24. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/deviceio.py +0 -0
  25. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/devicemgmt.py +0 -0
  26. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/display.py +0 -0
  27. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/doorcontrol.py +0 -0
  28. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/events.py +0 -0
  29. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/notification.py +0 -0
  30. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/pullpoint.py +0 -0
  31. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/events/subscription.py +0 -0
  32. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/imaging.py +0 -0
  33. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/media.py +0 -0
  34. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/media2.py +0 -0
  35. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/provisioning.py +0 -0
  36. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/ptz.py +0 -0
  37. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/receiver.py +0 -0
  38. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/recording.py +0 -0
  39. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/replay.py +0 -0
  40. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/schedule.py +0 -0
  41. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/search.py +0 -0
  42. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/advancedsecurity.py +0 -0
  43. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/authorizationserver.py +0 -0
  44. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/dot1x.py +0 -0
  45. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/jwt.py +0 -0
  46. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/keystore.py +0 -0
  47. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/mediasigning.py +0 -0
  48. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/security/tlsserver.py +0 -0
  49. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/thermal.py +0 -0
  50. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/services/uplink.py +0 -0
  51. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/error_handlers.py +0 -0
  52. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/exceptions.py +0 -0
  53. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/utils/wsdl.py +0 -0
  54. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  55. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  56. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  57. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  58. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  59. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  60. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  61. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  62. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  63. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  64. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  65. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  66. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  67. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  68. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  69. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  70. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  71. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/display.wsdl +0 -0
  72. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  73. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  74. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  75. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  76. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  77. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  78. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  79. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  80. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  81. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  82. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  83. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  84. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/recording.wsdl +0 -0
  85. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/replay.wsdl +0 -0
  86. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  87. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  88. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  89. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  90. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  91. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/search.wsdl +0 -0
  92. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  93. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  94. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  95. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  96. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  97. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  98. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  99. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/index.html +0 -0
  100. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  101. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  102. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  103. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  104. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  105. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  106. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  107. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  108. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  109. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  110. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  111. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  112. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/SOURCES.txt +0 -0
  113. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/dependency_links.txt +0 -0
  114. {onvif_python-0.0.7 → onvif_python-0.0.8}/onvif_python.egg-info/top_level.txt +0 -0
  115. {onvif_python-0.0.7 → onvif_python-0.0.8}/setup.cfg +0 -0
  116. {onvif_python-0.0.7 → onvif_python-0.0.8}/setup.py +0 -0
  117. {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_client.py +0 -0
  118. {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_core.py +0 -0
  119. {onvif_python-0.0.7 → onvif_python-0.0.8}/tests/test_exceptions.py +0 -0
  120. {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.7
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.2.1
35
- Requires-Dist: requests>=2.28.0
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
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
45
45
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
46
- [![Release](https://img.shields.io/badge/Release-v0.0.7-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
46
+ [![Release](https://img.shields.io/badge/Release-v0.0.8-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
47
47
  <br>
48
- [![PyPI](https://img.shields.io/badge/PyPI-0.0.7-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
48
+ [![PyPI](https://img.shields.io/badge/PyPI-0.0.8-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
49
49
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](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
- ### Basic Parameters
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
- ### Connection Parameters
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
- ### Caching Parameters
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
- ### Feature Parameters
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
- ### Cache Modes
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 | ⚡⚡⚡ Fast | 💾 High | 🧠 High |
194
- | `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | ⚡⚡ Medium | 💾 Medium | 🧠 Low |
195
- | `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | ⚡⚡ Medium | 💾 None | 🧠 Medium |
196
- | `CacheMode.NONE` | No caching | Testing, debugging | ⚡ Slow | 💾 None | 🧠 Low |
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
- ### Usage Examples
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
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
4
4
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
5
- [![Release](https://img.shields.io/badge/Release-v0.0.7-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
5
+ [![Release](https://img.shields.io/badge/Release-v0.0.8-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
6
6
  <br>
7
- [![PyPI](https://img.shields.io/badge/PyPI-0.0.7-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
7
+ [![PyPI](https://img.shields.io/badge/PyPI-0.0.8-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
8
8
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](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
- ### Basic Parameters
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
- ### Connection Parameters
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
- ### Caching Parameters
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
- ### Feature Parameters
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
- ### Cache Modes
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 | ⚡⚡⚡ Fast | 💾 High | 🧠 High |
153
- | `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | ⚡⚡ Medium | 💾 Medium | 🧠 Low |
154
- | `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | ⚡⚡ Medium | 💾 None | 🧠 Medium |
155
- | `CacheMode.NONE` | No caching | Testing, debugging | ⚡ Slow | 💾 None | 🧠 Low |
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
- ### Usage Examples
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.
@@ -1,3 +1,5 @@
1
+ # onvif/services/__init__.py
2
+
1
3
  from .devicemgmt import Device
2
4
  from .events.events import Events
3
5
  from .events.pullpoint import PullPoint
@@ -1,3 +1,5 @@
1
+ # onvif/utils/__init__.py
2
+
1
3
  from .wsdl import ONVIFWSDL
2
4
  from .exceptions import ONVIFOperationException
3
5
  from .zeep import ZeepPatcher
@@ -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.last_sent_xml = etree.tostring(
42
- envelope, pretty_print=self.pretty_print, encoding="unicode"
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.last_received_xml = etree.tostring(
62
- envelope, pretty_print=self.pretty_print, encoding="unicode"
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
- # Check if child has attributes (common in ONVIF capabilities)
463
+ parsed = {}
464
+
465
+ # Parse child's own attributes first (if any)
309
466
  if child.attrib:
310
- # Parse attributes as a dict
311
- attr_dict = {
312
- k: ZeepPatcher.parse_text_value(v)
313
- for k, v in child.attrib.items()
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)