onvif-python 0.2.0__tar.gz → 0.2.2__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 (133) hide show
  1. {onvif_python-0.2.0/onvif_python.egg-info → onvif_python-0.2.2}/PKG-INFO +6 -6
  2. {onvif_python-0.2.0 → onvif_python-0.2.2}/README.md +5 -5
  3. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/__init__.py +2 -0
  4. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/cli/interactive.py +2 -2
  5. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/cli/main.py +17 -4
  6. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/cli/utils.py +95 -1
  7. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/client.py +21 -5
  8. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/operator.py +1 -3
  9. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/advancedsecurity.py +1 -1
  10. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/__init__.py +2 -0
  11. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/discovery.py +1 -0
  12. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/error_handlers.py +1 -0
  13. onvif_python-0.2.2/onvif/utils/parser.py +136 -0
  14. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/service.py +5 -9
  15. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/wsdl.py +1 -0
  16. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/xml_capture.py +1 -9
  17. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/zeep.py +39 -3
  18. {onvif_python-0.2.0 → onvif_python-0.2.2/onvif_python.egg-info}/PKG-INFO +6 -6
  19. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif_python.egg-info/SOURCES.txt +1 -0
  20. {onvif_python-0.2.0 → onvif_python-0.2.2}/pyproject.toml +1 -1
  21. {onvif_python-0.2.0 → onvif_python-0.2.2}/LICENSE.md +0 -0
  22. {onvif_python-0.2.0 → onvif_python-0.2.2}/MANIFEST.in +0 -0
  23. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/cli/__init__.py +0 -0
  24. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/cli/__main__.py +0 -0
  25. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/db/companies.db +0 -0
  26. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/db/products.db +0 -0
  27. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/__init__.py +0 -0
  28. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/accesscontrol.py +0 -0
  29. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/accessrules.py +0 -0
  30. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/actionengine.py +0 -0
  31. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/analytics/analytics.py +0 -0
  32. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/analytics/ruleengine.py +0 -0
  33. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/analyticsdevice.py +0 -0
  34. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/appmgmt.py +0 -0
  35. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/authenticationbehavior.py +0 -0
  36. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/credential.py +0 -0
  37. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/deviceio.py +0 -0
  38. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/devicemgmt.py +0 -0
  39. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/display.py +0 -0
  40. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/doorcontrol.py +0 -0
  41. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/events/events.py +0 -0
  42. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/events/notification.py +0 -0
  43. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/events/pullpoint.py +0 -0
  44. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/events/subscription.py +0 -0
  45. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/imaging.py +0 -0
  46. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/media.py +0 -0
  47. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/media2.py +0 -0
  48. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/provisioning.py +0 -0
  49. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/ptz.py +0 -0
  50. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/receiver.py +0 -0
  51. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/recording.py +0 -0
  52. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/replay.py +0 -0
  53. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/schedule.py +0 -0
  54. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/search.py +0 -0
  55. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/authorizationserver.py +0 -0
  56. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/dot1x.py +0 -0
  57. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/jwt.py +0 -0
  58. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/keystore.py +0 -0
  59. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/mediasigning.py +0 -0
  60. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/security/tlsserver.py +0 -0
  61. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/thermal.py +0 -0
  62. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/services/uplink.py +0 -0
  63. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/utils/exceptions.py +0 -0
  64. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/LICENSE.md +0 -0
  65. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  66. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  67. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  68. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  69. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  70. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  71. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  72. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  73. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  74. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  75. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  76. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  77. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  78. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  79. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  80. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  81. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  82. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/display.wsdl +0 -0
  83. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  84. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  85. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  86. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  87. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  88. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  89. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  90. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  91. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  92. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  93. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  94. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  95. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/recording.wsdl +0 -0
  96. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/replay.wsdl +0 -0
  97. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  98. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  99. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  100. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  101. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  102. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/search.wsdl +0 -0
  103. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  104. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  105. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  106. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  107. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  108. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  109. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  110. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/index.html +0 -0
  111. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  112. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  113. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  114. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  115. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  116. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  117. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  118. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  119. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  120. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  121. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  122. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  123. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif_python.egg-info/dependency_links.txt +0 -0
  124. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif_python.egg-info/entry_points.txt +0 -0
  125. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif_python.egg-info/requires.txt +0 -0
  126. {onvif_python-0.2.0 → onvif_python-0.2.2}/onvif_python.egg-info/top_level.txt +0 -0
  127. {onvif_python-0.2.0 → onvif_python-0.2.2}/setup.cfg +0 -0
  128. {onvif_python-0.2.0 → onvif_python-0.2.2}/setup.py +0 -0
  129. {onvif_python-0.2.0 → onvif_python-0.2.2}/tests/test_client.py +0 -0
  130. {onvif_python-0.2.0 → onvif_python-0.2.2}/tests/test_core.py +0 -0
  131. {onvif_python-0.2.0 → onvif_python-0.2.2}/tests/test_error_handlers.py +0 -0
  132. {onvif_python-0.2.0 → onvif_python-0.2.2}/tests/test_exceptions.py +0 -0
  133. {onvif_python-0.2.0 → onvif_python-0.2.2}/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.2.0
3
+ Version: 0.2.2
4
4
  Summary: A modern Python library for ONVIF-compliant devices
5
5
  Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
6
6
  Maintainer: Kaburagi
@@ -47,7 +47,7 @@ Dynamic: license-file
47
47
 
48
48
  [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
49
49
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
50
- [![PyPI](https://img.shields.io/badge/PyPI-0.2.0-orange?logo=archive)](https://pypi.org/project/onvif-python/)
50
+ [![PyPI](https://img.shields.io/badge/PyPI-0.2.2-orange?logo=archive)](https://pypi.org/project/onvif-python/)
51
51
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
52
52
  <br>
53
53
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
@@ -356,7 +356,7 @@ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password
356
356
  [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
357
357
  [service] [method] [params ...]
358
358
 
359
- ONVIF Terminal Client — v0.2.0
359
+ ONVIF Terminal Client — v0.2.2
360
360
  https://github.com/nirsimetri/onvif-python
361
361
 
362
362
  positional arguments:
@@ -429,7 +429,7 @@ Examples:
429
429
  <summary><b>2. Interactive Shell</b></summary>
430
430
 
431
431
  ```bash
432
- ONVIF Interactive Shell — v0.2.0
432
+ ONVIF Interactive Shell — v0.2.2
433
433
  https://github.com/nirsimetri/onvif-python
434
434
 
435
435
  Basic Commands:
@@ -1108,9 +1108,9 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1108
1108
  2. **Security (Advanced Security)**
1109
1109
  - **Root:** `AdvancedSecurityServiceBinding`
1110
1110
  - **Sub-bindings:**
1111
+ - `JWTBinding`
1111
1112
  - `AuthorizationServerBinding`
1112
1113
  - `KeystoreBinding`
1113
- - `JWTBinding`
1114
1114
  - `Dot1XBinding`
1115
1115
  - `TLSServerBinding`
1116
1116
  - `MediaSigningBinding`
@@ -1118,9 +1118,9 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1118
1118
  Usage in library:
1119
1119
  ```python
1120
1120
  client.security() # root binding
1121
+ client.jwt() # sub-binding accessor
1121
1122
  client.authorizationserver(xaddr) # sub-binding accessor (requires xAddr)
1122
1123
  client.keystore(xaddr) # ..
1123
- client.jwt(xaddr)
1124
1124
  client.dot1x(xaddr)
1125
1125
  client.tlsserver(xaddr)
1126
1126
  client.mediasigning(xaddr)
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
6
6
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
7
- [![PyPI](https://img.shields.io/badge/PyPI-0.2.0-orange?logo=archive)](https://pypi.org/project/onvif-python/)
7
+ [![PyPI](https://img.shields.io/badge/PyPI-0.2.2-orange?logo=archive)](https://pypi.org/project/onvif-python/)
8
8
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
9
9
  <br>
10
10
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
@@ -313,7 +313,7 @@ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password
313
313
  [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
314
314
  [service] [method] [params ...]
315
315
 
316
- ONVIF Terminal Client — v0.2.0
316
+ ONVIF Terminal Client — v0.2.2
317
317
  https://github.com/nirsimetri/onvif-python
318
318
 
319
319
  positional arguments:
@@ -386,7 +386,7 @@ Examples:
386
386
  <summary><b>2. Interactive Shell</b></summary>
387
387
 
388
388
  ```bash
389
- ONVIF Interactive Shell — v0.2.0
389
+ ONVIF Interactive Shell — v0.2.2
390
390
  https://github.com/nirsimetri/onvif-python
391
391
 
392
392
  Basic Commands:
@@ -1065,9 +1065,9 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1065
1065
  2. **Security (Advanced Security)**
1066
1066
  - **Root:** `AdvancedSecurityServiceBinding`
1067
1067
  - **Sub-bindings:**
1068
+ - `JWTBinding`
1068
1069
  - `AuthorizationServerBinding`
1069
1070
  - `KeystoreBinding`
1070
- - `JWTBinding`
1071
1071
  - `Dot1XBinding`
1072
1072
  - `TLSServerBinding`
1073
1073
  - `MediaSigningBinding`
@@ -1075,9 +1075,9 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1075
1075
  Usage in library:
1076
1076
  ```python
1077
1077
  client.security() # root binding
1078
+ client.jwt() # sub-binding accessor
1078
1079
  client.authorizationserver(xaddr) # sub-binding accessor (requires xAddr)
1079
1080
  client.keystore(xaddr) # ..
1080
- client.jwt(xaddr)
1081
1081
  client.dot1x(xaddr)
1082
1082
  client.tlsserver(xaddr)
1083
1083
  client.mediasigning(xaddr)
@@ -8,6 +8,7 @@ from .utils import (
8
8
  ONVIFErrorHandler,
9
9
  ZeepPatcher,
10
10
  ONVIFDiscovery,
11
+ ONVIFParser,
11
12
  )
12
13
  from .cli import main as ONVIFCLI
13
14
 
@@ -21,4 +22,5 @@ __all__ = [
21
22
  "ZeepPatcher",
22
23
  "ONVIFCLI",
23
24
  "ONVIFDiscovery",
25
+ "ONVIFParser",
24
26
  ]
@@ -194,7 +194,7 @@ class InteractiveShell(cmd.Cmd):
194
194
  " / __ \\/ | / / | / / _/ ____/",
195
195
  " / / / / |/ /| | / // // /_ ",
196
196
  "/ /_/ / /| / | |/ // // __/ ",
197
- "\\____/_/ |_/ |___/___/_/ v0.2.0",
197
+ "\\____/_/ |_/ |___/___/_/ v0.2.2",
198
198
  " ",
199
199
  ]
200
200
 
@@ -1400,7 +1400,7 @@ class InteractiveShell(cmd.Cmd):
1400
1400
  super().do_help(line)
1401
1401
  else:
1402
1402
  help_text = f"""
1403
- {colorize('ONVIF Interactive Shell — v0.2.0', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1403
+ {colorize('ONVIF Interactive Shell — v0.2.2', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1404
1404
 
1405
1405
  {colorize('Basic Commands:', 'yellow')}
1406
1406
  capabilities, caps - Show device capabilities
@@ -21,7 +21,7 @@ def create_parser():
21
21
  """Create argument parser for ONVIF CLI"""
22
22
  parser = argparse.ArgumentParser(
23
23
  prog="onvif",
24
- description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.0\nhttps://github.com/nirsimetri/onvif-python",
24
+ description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.2\nhttps://github.com/nirsimetri/onvif-python",
25
25
  formatter_class=argparse.RawDescriptionHelpFormatter,
26
26
  epilog=f"""
27
27
  Examples:
@@ -198,10 +198,14 @@ def search_products(search_term: str, page: int = 1, per_page: int = 20) -> None
198
198
  WHERE LOWER(model) LIKE LOWER(?)
199
199
  OR LOWER(post_title) LIKE LOWER(?)
200
200
  OR LOWER(company_name) LIKE LOWER(?)
201
+ OR LOWER(product_category) LIKE LOWER(?)
201
202
  """
202
203
 
203
204
  search_pattern = f"%{search_term}%"
204
- cursor.execute(count_query, (search_pattern, search_pattern, search_pattern))
205
+ cursor.execute(
206
+ count_query,
207
+ (search_pattern, search_pattern, search_pattern, search_pattern),
208
+ )
205
209
  total_count = cursor.fetchone()[0]
206
210
 
207
211
  if total_count == 0:
@@ -229,12 +233,21 @@ def search_products(search_term: str, page: int = 1, per_page: int = 20) -> None
229
233
  WHERE LOWER(model) LIKE LOWER(?)
230
234
  OR LOWER(post_title) LIKE LOWER(?)
231
235
  OR LOWER(company_name) LIKE LOWER(?)
236
+ OR LOWER(product_category) LIKE LOWER(?)
232
237
  ORDER BY test_date DESC
233
238
  LIMIT ? OFFSET ?
234
239
  """
235
240
 
236
241
  cursor.execute(
237
- query, (search_pattern, search_pattern, search_pattern, per_page, offset)
242
+ query,
243
+ (
244
+ search_pattern,
245
+ search_pattern,
246
+ search_pattern,
247
+ search_pattern,
248
+ per_page,
249
+ offset,
250
+ ),
238
251
  )
239
252
  results = cursor.fetchall()
240
253
 
@@ -448,7 +461,7 @@ def main():
448
461
 
449
462
  # Show ONVIF CLI version
450
463
  if args.version:
451
- print(colorize("0.2.0", "yellow"))
464
+ print(colorize("0.2.2", "yellow"))
452
465
  sys.exit(0)
453
466
 
454
467
  # Handle product search
@@ -618,7 +618,7 @@ def get_device_available_services(client) -> list:
618
618
  # Security namespace has multiple bindings
619
619
  available_services.extend(
620
620
  [
621
- "advancedsecurity",
621
+ "security",
622
622
  "jwt",
623
623
  "keystore",
624
624
  "tlsserver",
@@ -628,6 +628,100 @@ def get_device_available_services(client) -> list:
628
628
  ]
629
629
  )
630
630
 
631
+ # Additional check: Try to call security.GetServiceCapabilities() to verify availability
632
+ # This is necessary because Security service might not be reported in GetServices/GetCapabilities
633
+ # Use caching to avoid calling GetServiceCapabilities repeatedly
634
+ if "security" not in available_services:
635
+ # Check if we've already tried to get security capabilities
636
+ if not hasattr(client, "_security_capabilities_checked"):
637
+ client._security_capabilities_checked = False
638
+ client._security_capabilities = None
639
+
640
+ if not client._security_capabilities_checked:
641
+ # First time check - call GetServiceCapabilities and cache result
642
+ try:
643
+ security_service = client.security()
644
+ # Try to call GetServiceCapabilities to verify the service is actually available
645
+ caps = security_service.GetServiceCapabilities()
646
+
647
+ # Cache the capabilities for future use
648
+ client._security_capabilities = caps
649
+ client._security_capabilities_checked = True
650
+
651
+ except Exception:
652
+ # Security service not available, mark as checked
653
+ client._security_capabilities_checked = True
654
+ client._security_capabilities = None
655
+
656
+ # Use cached capabilities
657
+ if client._security_capabilities is not None:
658
+ caps = client._security_capabilities
659
+
660
+ # If successful, add main security service
661
+ available_services.append("security")
662
+
663
+ # Check each sub-service capability to determine availability
664
+ # Only add sub-services if their corresponding capability is not None
665
+ if (
666
+ hasattr(caps, "KeystoreCapabilities")
667
+ and caps.KeystoreCapabilities is not None
668
+ ):
669
+ available_services.append("keystore")
670
+
671
+ if (
672
+ hasattr(caps, "TLSServerCapabilities")
673
+ and caps.TLSServerCapabilities is not None
674
+ ):
675
+ available_services.append("tlsserver")
676
+
677
+ if (
678
+ hasattr(caps, "Dot1XCapabilities")
679
+ and caps.Dot1XCapabilities is not None
680
+ ):
681
+ available_services.append("dot1x")
682
+
683
+ if (
684
+ hasattr(caps, "AuthorizationServer")
685
+ and caps.AuthorizationServer is not None
686
+ ):
687
+ available_services.append("authorizationserver")
688
+
689
+ if hasattr(caps, "MediaSigning") and caps.MediaSigning is not None:
690
+ available_services.append("mediasigning")
691
+
692
+ # Additional check for JWT service: Try to call jwt.GetJWTConfiguration()
693
+ # JWT doesn't have a capability in GetServiceCapabilities, so we need to test it directly
694
+ if "jwt" not in available_services:
695
+ # Check if we've already tried to get JWT availability
696
+ if not hasattr(client, "_jwt_checked"):
697
+ client._jwt_checked = False
698
+ client._jwt_available = None
699
+
700
+ if not client._jwt_checked:
701
+ # First time check - try to call GetJWTConfiguration and cache result
702
+ try:
703
+ # JWT service requires xaddr from security service
704
+ # Try to construct xaddr from security service endpoint
705
+ protocol = "https" if client.common_args["use_https"] else "http"
706
+ default_xaddr = f"{protocol}://{client.common_args['host']}:{client.common_args['port']}/onvif/AdvancedSecurity"
707
+
708
+ # Try to call GetJWTConfiguration to verify JWT is available
709
+ jwt_service = client.jwt(xaddr=default_xaddr)
710
+ jwt_service.GetJWTConfiguration()
711
+
712
+ # If successful, JWT is available
713
+ client._jwt_available = True
714
+ client._jwt_checked = True
715
+
716
+ except Exception:
717
+ # JWT service not available, mark as checked
718
+ client._jwt_checked = True
719
+ client._jwt_available = False
720
+
721
+ # Use cached JWT availability
722
+ if client._jwt_available:
723
+ available_services.append("jwt")
724
+
631
725
  return sorted(list(set(available_services))) # Remove duplicates and sort
632
726
 
633
727
 
@@ -111,6 +111,7 @@ class ONVIFClient:
111
111
  apply_patch: bool = True,
112
112
  capture_xml: bool = False,
113
113
  wsdl_dir: str = None,
114
+ plugins: list = None,
114
115
  ):
115
116
  logger.info(f"Initializing ONVIF client for {host}:{port}")
116
117
  logger.debug(
@@ -131,6 +132,15 @@ class ONVIFClient:
131
132
  logger.debug("Enabling XML capture plugin")
132
133
  self.xml_plugin = XMLCapturePlugin()
133
134
 
135
+ # Merge user plugins with xml_plugin
136
+ all_plugins = []
137
+ if plugins:
138
+ logger.debug(f"Adding {len(plugins)} user-provided plugins")
139
+ all_plugins.extend(plugins)
140
+ if self.xml_plugin:
141
+ logger.debug("Adding XML capture plugin")
142
+ all_plugins.append(self.xml_plugin)
143
+
134
144
  # Store custom WSDL directory if provided
135
145
  self.wsdl_dir = wsdl_dir
136
146
  if wsdl_dir:
@@ -148,7 +158,7 @@ class ONVIFClient:
148
158
  "use_https": use_https,
149
159
  "verify_ssl": verify_ssl,
150
160
  "apply_patch": apply_patch,
151
- "plugins": [self.xml_plugin] if self.xml_plugin else None,
161
+ "plugins": all_plugins if all_plugins else None,
152
162
  }
153
163
 
154
164
  # Device Management (Core) service is always available
@@ -162,6 +172,14 @@ class ONVIFClient:
162
172
  # Temporary variable to hold capabilities
163
173
  self.capabilities = None
164
174
 
175
+ # Cache for security service capabilities (lazy loaded)
176
+ self._security_capabilities = None
177
+ self._security_capabilities_checked = False
178
+
179
+ # Cache for JWT service availability (lazy loaded)
180
+ self._jwt_available = None
181
+ self._jwt_checked = False
182
+
165
183
  try:
166
184
  # Try GetServices first (preferred method)
167
185
  logger.debug("Attempting GetServices call for service discovery")
@@ -722,17 +740,15 @@ class ONVIFClient:
722
740
  if self._security is None:
723
741
  logger.debug("Initializing Security service")
724
742
  self._security = AdvancedSecurity(
725
- xaddr=self._get_xaddr("advancedsecurity", "Security"),
726
743
  **self.common_args,
727
744
  )
728
745
  return self._security
729
746
 
730
747
  @service
731
- def jwt(self, xaddr):
748
+ def jwt(self):
732
749
  if self._jwt is None:
733
750
  logger.debug("Initializing JWT service")
734
- xaddr = self._rewrite_xaddr_if_needed(xaddr)
735
- self._jwt = JWT(xaddr=xaddr, **self.common_args)
751
+ self._jwt = JWT(**self.common_args)
736
752
  return self._jwt
737
753
 
738
754
  @service
@@ -15,6 +15,7 @@ from zeep.wsse.username import UsernameToken
15
15
  from .utils import ONVIFOperationException, ZeepPatcher
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
+ logger.addHandler(logging.NullHandler())
18
19
 
19
20
 
20
21
  class CacheMode(Enum):
@@ -194,7 +195,6 @@ class ONVIFOperator:
194
195
  try:
195
196
  func = getattr(self.service, method)
196
197
  except AttributeError as e:
197
- logger.error(f"Method {method} not found in {self.service_name} service")
198
198
  raise ONVIFOperationException(operation=method, original_exception=e)
199
199
 
200
200
  try:
@@ -207,10 +207,8 @@ class ONVIFOperator:
207
207
  return result
208
208
 
209
209
  except Fault as e:
210
- logger.error(f"SOAP Fault in {self.service_name}.{method}: {e}")
211
210
  raise ONVIFOperationException(operation=method, original_exception=e)
212
211
  except Exception as e:
213
- logger.error(f"ONVIF call error in {self.service_name}.{method}: {e}")
214
212
  raise ONVIFOperationException(operation=method, original_exception=e)
215
213
 
216
214
  def create_type(self, type_name: str):
@@ -15,7 +15,7 @@ class AdvancedSecurity(ONVIFService):
15
15
  self.operator = ONVIFOperator(
16
16
  definition["path"],
17
17
  binding=f"{{{definition['namespace']}}}{definition['binding']}",
18
- service_path="Security", # fallback
18
+ service_path="device_service", # fallback
19
19
  xaddr=xaddr,
20
20
  **kwargs,
21
21
  )
@@ -7,6 +7,7 @@ from .xml_capture import XMLCapturePlugin
7
7
  from .error_handlers import ONVIFErrorHandler
8
8
  from .discovery import ONVIFDiscovery
9
9
  from .service import ONVIFService
10
+ from .parser import ONVIFParser
10
11
 
11
12
 
12
13
  __all__ = [
@@ -17,4 +18,5 @@ __all__ = [
17
18
  "ONVIFErrorHandler",
18
19
  "ONVIFDiscovery",
19
20
  "ONVIFService",
21
+ "ONVIFParser",
20
22
  ]
@@ -8,6 +8,7 @@ from lxml import etree
8
8
  from typing import List, Dict, Any, Optional
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
+ logger.addHandler(logging.NullHandler())
11
12
 
12
13
 
13
14
  class ONVIFDiscovery:
@@ -5,6 +5,7 @@ from zeep.exceptions import Fault
5
5
  from .exceptions import ONVIFOperationException
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
+ logger.addHandler(logging.NullHandler())
8
9
 
9
10
 
10
11
  class ONVIFErrorHandler:
@@ -0,0 +1,136 @@
1
+ # onvif/utils/parser.py
2
+
3
+ import logging
4
+ from typing import List, Dict, Optional
5
+ from zeep import Plugin
6
+
7
+ logger = logging.getLogger(__name__)
8
+ logger.addHandler(logging.NullHandler())
9
+
10
+
11
+ class ONVIFParser(Plugin):
12
+ """
13
+ Lightweight Zeep plugin to extract XML elements from SOAP responses using XPath.
14
+
15
+ This plugin extracts specific XML elements from raw SOAP responses before zeep
16
+ parses them into Python objects. This is useful for extracting data that zeep
17
+ doesn't parse correctly, such as simpleContent elements with attributes (e.g., Topic).
18
+
19
+ Unlike XMLCapturePlugin which stores full XML history in memory, ONVIFParser only
20
+ extracts and caches specified element texts from the last response.
21
+
22
+ Usage Example 1 - Extract Topic from event notifications:
23
+ >>> from onvif import ONVIFClient
24
+ >>> from onvif.utils import ONVIFParser
25
+ >>>
26
+ >>> # Create parser to extract Topic elements
27
+ >>> parser = ONVIFParser(extract_xpaths={
28
+ ... 'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic'
29
+ ... })
30
+ >>>
31
+ >>> # Pass parser to client as plugin
32
+ >>> client = ONVIFClient(host, port, user, pass, plugins=[parser])
33
+ >>>
34
+ >>> # Make SOAP call
35
+ >>> pullpoint = client.pullpoint(subscription)
36
+ >>> msgs = pullpoint.PullMessages(Timeout="PT5S", MessageLimit=10)
37
+ >>>
38
+ >>> # Extract topic texts (cache auto-cleared on next SOAP call)
39
+ >>> topics = parser.get_extracted_texts('topic', count=10)
40
+ >>> for topic in topics:
41
+ ... print(f"Topic: {topic}")
42
+
43
+ Usage Example 2 - Extract multiple elements:
44
+ >>> parser = ONVIFParser(extract_xpaths={
45
+ ... 'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic',
46
+ ... 'custom': './/ns:CustomElement'
47
+ ... })
48
+ >>> client = ONVIFClient(host, port, user, pass, plugins=[parser])
49
+ >>>
50
+ >>> # After SOAP call
51
+ >>> topics = parser.get_extracted_texts('topic', count=5)
52
+ >>> customs = parser.get_extracted_texts('custom', count=5)
53
+
54
+ Notes:
55
+ - Uses ingress() hook to access raw XML before zeep parsing
56
+ - Cache automatically cleared on each SOAP response
57
+ - Thread-safe for single client usage
58
+ - Works with any XPath expression
59
+ """
60
+
61
+ def __init__(self, extract_xpaths: Dict[str, str]):
62
+ """
63
+ Initialize XML element parser.
64
+
65
+ Args:
66
+ extract_xpaths: Dictionary mapping names to XPath expressions. XPath expressions will be used to find elements in SOAP response.
67
+ Example: {
68
+ 'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic',
69
+ 'custom': './/ns:CustomElement'
70
+ }
71
+ """
72
+ self.extract_xpaths = extract_xpaths
73
+ self._extracted_elements = {}
74
+
75
+ logger.debug(
76
+ f"ONVIFParser initialized with XPaths: {list(extract_xpaths.keys())}"
77
+ )
78
+
79
+ def ingress(self, envelope, http_headers, operation):
80
+ """
81
+ Zeep plugin hook - called when SOAP response is received.
82
+
83
+ Extracts element texts from raw XML envelope using configured XPath expressions.
84
+ The envelope at this stage is an lxml Element tree, allowing XPath queries.
85
+
86
+ Cache is automatically cleared before extracting new elements to prevent memory accumulation.
87
+
88
+ Args:
89
+ envelope: lxml Element representing SOAP envelope
90
+ http_headers: HTTP response headers
91
+ operation: Zeep operation being executed
92
+
93
+ Returns:
94
+ Tuple of (envelope, http_headers) to pass to next plugin
95
+ """
96
+ # Auto-clear cache from previous response
97
+ self._extracted_elements = {}
98
+
99
+ try:
100
+ # Extract elements using XPath from raw XML envelope
101
+ for name, xpath in self.extract_xpaths.items():
102
+ elements = envelope.findall(xpath)
103
+
104
+ # Extract text content from found elements
105
+ texts = [elem.text for elem in elements if elem.text]
106
+
107
+ if texts:
108
+ self._extracted_elements[name] = texts
109
+ logger.debug(
110
+ f"ONVIFParser: Extracted {len(texts)} '{name}' elements"
111
+ )
112
+
113
+ except Exception as e:
114
+ logger.warning(f"ONVIFParser: Failed to extract elements: {e}")
115
+
116
+ return envelope, http_headers
117
+
118
+ def get_extracted_texts(self, name: str, count: int) -> List[Optional[str]]:
119
+ """
120
+ Get extracted element texts by name.
121
+
122
+ Args:
123
+ name: Name of the extracted elements (key from extract_xpaths dict)
124
+ count: Number of elements to return
125
+
126
+ Returns:
127
+ List of element text values, padded with None if fewer elements were found.
128
+ Example: If 3 elements found but count=5, returns [text1, text2, text3, None, None]
129
+ """
130
+ texts = self._extracted_elements.get(name, [])[:count]
131
+
132
+ # Pad with None if not enough elements
133
+ while len(texts) < count:
134
+ texts.append(None)
135
+
136
+ return texts
@@ -4,6 +4,7 @@ import logging
4
4
  from .exceptions import ONVIFOperationException
5
5
 
6
6
  logger = logging.getLogger(__name__)
7
+ logger.addHandler(logging.NullHandler())
7
8
 
8
9
 
9
10
  def _is_zeep_object(obj):
@@ -40,13 +41,6 @@ class ONVIFService:
40
41
  3. Is not a private method (doesn't start with underscore)
41
42
  4. Is not an internal attribute (like 'operator')
42
43
 
43
- Benefits:
44
- 1. **DRY Principle**: No need to repeat error handling in every method
45
- 2. **Consistency**: All ONVIF operations behave the same way
46
- 3. **Maintainability**: Error handling logic in one place
47
- 4. **Debuggability**: Always know which operation failed
48
- 5. **Transparency**: No boilerplate code in service implementations
49
-
50
44
  Notes:
51
45
  - This is an abstract base class - don't instantiate directly
52
46
  - Subclasses must implement their own __init__ and ONVIF methods
@@ -113,11 +107,13 @@ class ONVIFService:
113
107
  return result
114
108
  except ONVIFOperationException as oe:
115
109
  # Re-raise ONVIF exceptions as-is
116
- logger.error(f"ONVIF operation exception in {name}: {oe}")
110
+ service_name = getattr(self.operator, "service_name", "Unknown")
111
+ logger.error(f"{service_name}.{name}: {oe}")
117
112
  raise
118
113
  except Exception as e:
119
114
  # Convert any other exception (including TypeError) to ONVIFOperationException
120
- logger.error(f"ONVIF operation exception in {name}: {e}")
115
+ service_name = getattr(self.operator, "service_name", "Unknown")
116
+ logger.error(f"{service_name}.{name}: {e}")
121
117
  raise ONVIFOperationException(name, e)
122
118
 
123
119
  return wrapped_method
@@ -4,6 +4,7 @@ import os
4
4
  import logging
5
5
 
6
6
  logger = logging.getLogger(__name__)
7
+ logger.addHandler(logging.NullHandler())
7
8
 
8
9
 
9
10
  class ONVIFWSDL:
@@ -5,6 +5,7 @@ from zeep import Plugin
5
5
  from lxml import etree
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
+ logger.addHandler(logging.NullHandler())
8
9
 
9
10
 
10
11
  class XMLCapturePlugin(Plugin):
@@ -21,15 +22,6 @@ class XMLCapturePlugin(Plugin):
21
22
  - Operation names for context
22
23
  - Complete history of all transactions
23
24
 
24
- Key Features:
25
- - Pretty-printed XML with proper indentation
26
- - Last request/response quick access
27
- - Full transaction history storage
28
- - HTTP header capture
29
- - Operation name tracking
30
- - File export capability
31
- - Secure XML parsing (XXE protection)
32
-
33
25
  Use Cases:
34
26
  1. **Debugging**: See exact SOAP messages being sent/received
35
27
  2. **Learning**: Understand ONVIF protocol structure