Plugins
Auxiliary custom Zeep plugins outside the core scope.
All classes in this module inherit from Zeep's Plugin (zeep.plugins) class.
ONVIFParser
Bases: Plugin
Lightweight Zeep plugin for extracting XML elements from SOAP responses.
The parser extracts text from XML elements matching configured XPath
expressions before Zeep parses the SOAP response. This is useful for
extracting elements that Zeep does not parse correctly, such as
simpleContent elements with attributes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extract_xpaths
|
dict[str, str]
|
Dictionary mapping extraction names to XPath expressions. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
extract_xpaths |
dict[str, str]
|
XPath expressions used to extract elements from SOAP responses. |
_extracted_elements |
dict[str, list[str | None]]
|
Extracted element texts from the most recent SOAP response, grouped by extraction name. |
Version History
- Available since
>=v0.2.2.
Usage
from onvif import ONVIFClient, ONVIFParser
parser = ONVIFParser({
"topic": ".//{http://docs.oasis-open.org/wsn/b-2}Topic",
})
client = ONVIFClient(
host, port, username, password, plugins=[parser]
)
subscription = client.events().CreatePullPointSubscription()
pullpoint = client.pullpoint(subscription)
msgs = pullpoint.PullMessages(
Timeout="PT5S",
MessageLimit=10,
)
topics = parser.get_extracted_texts(
"topic",
len(msgs.NotificationMessage),
)
Notes
- Uses Zeep's
ingress()hook to access the raw SOAP response. - The extraction cache is cleared before each SOAP response.
- Supports any XPath expression accepted by
lxml.
Source code in onvif\utils\plugins.py
class ONVIFParser(Plugin):
"""Lightweight Zeep plugin for extracting XML elements from SOAP responses.
The parser extracts text from XML elements matching configured XPath
expressions before Zeep parses the SOAP response. This is useful for
extracting elements that Zeep does not parse correctly, such as
`simpleContent` elements with attributes.
Args:
extract_xpaths: Dictionary mapping extraction names to XPath expressions.
Attributes:
extract_xpaths: XPath expressions used to extract elements from SOAP
responses.
_extracted_elements: Extracted element texts from the most recent SOAP
response, grouped by extraction name.
!!! tip "Version History"
- Available since [`>=v0.2.2`](/onvif-python/releases/#v0.2.2).
??? example "Usage"
```python linenums="1"
from onvif import ONVIFClient, ONVIFParser
parser = ONVIFParser({
"topic": ".//{http://docs.oasis-open.org/wsn/b-2}Topic",
})
client = ONVIFClient(
host, port, username, password, plugins=[parser]
)
subscription = client.events().CreatePullPointSubscription()
pullpoint = client.pullpoint(subscription)
msgs = pullpoint.PullMessages(
Timeout="PT5S",
MessageLimit=10,
)
topics = parser.get_extracted_texts(
"topic",
len(msgs.NotificationMessage),
)
```
??? note "Notes"
- Uses Zeep's `ingress()` hook to access the raw SOAP response.
- The extraction cache is cleared before each SOAP response.
- Supports any XPath expression accepted by `lxml`.
"""
def __init__(self, extract_xpaths: dict[str, str]):
"""Initialize XML element parser.
Args:
extract_xpaths: Dictionary mapping names to XPath expressions.
XPath expressions will be used to find elements in SOAP response.
Example:
```python
{
'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic',
'custom': './/ns:CustomElement'
}
```
"""
self.extract_xpaths: dict[str, str] = extract_xpaths
self._extracted_elements: dict[str, list[str | None]] = {}
logger.debug(
"ONVIFParser initialized with XPaths: %s", list(extract_xpaths.keys())
)
def ingress(
self, envelope: etree._Element, http_headers: dict[str, str], operation: Any
):
"""Process a SOAP response before Zeep deserializes it.
This Zeep ``ingress`` hook inspects the raw SOAP response envelope and
extracts text from elements matching the configured XPath expressions.
The extracted values are stored temporarily and can be retrieved with
`get_extracted_texts`.
The extraction cache is cleared before processing each response, so it
always contains values from the most recently processed SOAP response.
Args:
envelope: Raw SOAP response envelope as an `lxml.etree.Element`.
http_headers: HTTP response headers returned by the device.
operation: Zeep operation associated with the SOAP response.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
Returning both values allows Zeep to continue processing the response
and allows other plugins in the chain to process it as well.
"""
# Auto-clear cache from previous response
self._extracted_elements = {}
try:
# Extract elements using XPath from raw XML envelope
for name, xpath in self.extract_xpaths.items():
elements = envelope.findall(xpath)
# Extract text content from found elements
texts = [elem.text for elem in elements]
if texts:
self._extracted_elements[name] = texts
logger.debug(
"ONVIFParser: Extracted %d '%s' elements", len(texts), name
)
except (ValueError, TypeError, AttributeError) as e:
logger.warning("ONVIFParser: Failed to extract elements: %s", e)
return envelope, http_headers
def get_extracted_texts(self, name: str, count: int) -> list[str | None]:
"""Get extracted element texts by name.
Args:
name (str): Name of the extracted elements (key from `extract_xpaths` dict)
count (int): Number of elements to return
Returns:
List of element text values, padded with None if fewer elements were found.
Example: If 3 elements found but `count=5`, returns [text1, text2, text3, None, None]
"""
texts = self._extracted_elements.get(name, [])[:count]
# Pad with None if not enough elements
while len(texts) < count:
texts.append(None)
return texts
__init__(extract_xpaths: dict[str, str])
Initialize XML element parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extract_xpaths
|
dict[str, str]
|
Dictionary mapping names to XPath expressions. XPath expressions will be used to find elements in SOAP response. |
required |
Example
Source code in onvif\utils\plugins.py
def __init__(self, extract_xpaths: dict[str, str]):
"""Initialize XML element parser.
Args:
extract_xpaths: Dictionary mapping names to XPath expressions.
XPath expressions will be used to find elements in SOAP response.
Example:
```python
{
'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic',
'custom': './/ns:CustomElement'
}
```
"""
self.extract_xpaths: dict[str, str] = extract_xpaths
self._extracted_elements: dict[str, list[str | None]] = {}
logger.debug(
"ONVIFParser initialized with XPaths: %s", list(extract_xpaths.keys())
)
ingress(envelope: etree._Element, http_headers: dict[str, str], operation: Any)
Process a SOAP response before Zeep deserializes it.
This Zeep ingress hook inspects the raw SOAP response envelope and
extracts text from elements matching the configured XPath expressions.
The extracted values are stored temporarily and can be retrieved with
get_extracted_texts.
The extraction cache is cleared before processing each response, so it always contains values from the most recently processed SOAP response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
_Element
|
Raw SOAP response envelope as an |
required |
http_headers
|
dict[str, str]
|
HTTP response headers returned by the device. |
required |
operation
|
Any
|
Zeep operation associated with the SOAP response. |
required |
Returns:
| Type | Description |
|---|---|
|
A tuple containing the unchanged |
Source code in onvif\utils\plugins.py
def ingress(
self, envelope: etree._Element, http_headers: dict[str, str], operation: Any
):
"""Process a SOAP response before Zeep deserializes it.
This Zeep ``ingress`` hook inspects the raw SOAP response envelope and
extracts text from elements matching the configured XPath expressions.
The extracted values are stored temporarily and can be retrieved with
`get_extracted_texts`.
The extraction cache is cleared before processing each response, so it
always contains values from the most recently processed SOAP response.
Args:
envelope: Raw SOAP response envelope as an `lxml.etree.Element`.
http_headers: HTTP response headers returned by the device.
operation: Zeep operation associated with the SOAP response.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
Returning both values allows Zeep to continue processing the response
and allows other plugins in the chain to process it as well.
"""
# Auto-clear cache from previous response
self._extracted_elements = {}
try:
# Extract elements using XPath from raw XML envelope
for name, xpath in self.extract_xpaths.items():
elements = envelope.findall(xpath)
# Extract text content from found elements
texts = [elem.text for elem in elements]
if texts:
self._extracted_elements[name] = texts
logger.debug(
"ONVIFParser: Extracted %d '%s' elements", len(texts), name
)
except (ValueError, TypeError, AttributeError) as e:
logger.warning("ONVIFParser: Failed to extract elements: %s", e)
return envelope, http_headers
get_extracted_texts(name: str, count: int) -> list[str | None]
Get extracted element texts by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Name of the extracted elements (key from |
required |
count
|
int
|
Number of elements to return |
required |
Returns:
| Type | Description |
|---|---|
list[str | None]
|
List of element text values, padded with None if fewer elements were found.
Example: If 3 elements found but |
Source code in onvif\utils\plugins.py
def get_extracted_texts(self, name: str, count: int) -> list[str | None]:
"""Get extracted element texts by name.
Args:
name (str): Name of the extracted elements (key from `extract_xpaths` dict)
count (int): Number of elements to return
Returns:
List of element text values, padded with None if fewer elements were found.
Example: If 3 elements found but `count=5`, returns [text1, text2, text3, None, None]
"""
texts = self._extracted_elements.get(name, [])[:count]
# Pad with None if not enough elements
while len(texts) < count:
texts.append(None)
return texts
XMLCapturePlugin
Bases: Plugin
Zeep plugin to capture and inspect SOAP XML requests and responses.
This plugin intercepts SOAP communication between the ONVIF client and device, capturing raw XML for debugging, logging, and analysis purposes. It's invaluable for understanding SOAP message structure and troubleshooting device communication.
Attributes:
| Name | Type | Description |
|---|---|---|
pretty_print |
bool
|
Whether to format XML with indentation |
last_sent_xml |
str | None
|
Most recent request XML |
last_received_xml |
str | None
|
Most recent response XML |
last_operation |
str | None
|
Most recent operation name |
history |
list[dict[str, Any]]
|
All captured requests/responses with metadata |
The plugin automatically captures
- Outgoing SOAP requests (
egress) - Incoming SOAP responses (
ingress) - HTTP headers for both directions
- Operation names for context
- Complete history of all transactions
Use Cases
- Debugging: See exact SOAP messages being sent/received
- Learning: Understand ONVIF protocol structure
- Testing: Verify request format and response structure
- Documentation: Extract examples for documentation
- Troubleshooting: Diagnose device compatibility issues
- Development: Test SOAP message modifications
Performance Considerations
- Pretty printing adds minimal overhead (~5-10ms per request)
- History storage grows with each request (clear periodically)
- Large responses may consume significant memory
- Consider disabling in production for high-volume applications
History Item Structure
Notes
- Plugin is automatically created when
capture_xml=True - Captured XML includes SOAP envelope, headers, and body
- HTTP headers are captured as dictionaries
- History preserves chronological order
- Pretty printing uses lxml for reliable formatting
- All captured data is stored in memory
See Also
zeep.Plugin: Base class for zeep pluginsONVIFClient: Client that uses this pluginlxml.etree: XML processing library
Source code in onvif\utils\plugins.py
class XMLCapturePlugin(Plugin):
"""Zeep plugin to capture and inspect SOAP XML requests and responses.
This plugin intercepts SOAP communication between the ONVIF client and device,
capturing raw XML for debugging, logging, and analysis purposes. It's invaluable
for understanding SOAP message structure and troubleshooting device communication.
Attributes:
pretty_print (bool): Whether to format XML with indentation
last_sent_xml (str | None): Most recent request XML
last_received_xml (str | None): Most recent response XML
last_operation (str | None): Most recent operation name
history (list[dict[str, Any]]): All captured requests/responses with metadata
!!! tip "Version History"
- Available since [`>=v0.0.6`](/onvif-python/releases/#v0.0.6).
- Moved to `plugins` since [`>=v0.4.0`](/onvif-python/releases/#v0.4.0).
!!! abstract "The plugin automatically captures"
- Outgoing SOAP requests (`egress`)
- Incoming SOAP responses (`ingress`)
- HTTP headers for both directions
- Operation names for context
- Complete history of all transactions
!!! danger "Use Cases"
1. **Debugging**: See exact SOAP messages being sent/received
2. **Learning**: Understand ONVIF protocol structure
3. **Testing**: Verify request format and response structure
4. **Documentation**: Extract examples for documentation
5. **Troubleshooting**: Diagnose device compatibility issues
6. **Development**: Test SOAP message modifications
!!! warning "Performance Considerations"
- Pretty printing adds minimal overhead (~5-10ms per request)
- History storage grows with each request (clear periodically)
- Large responses may consume significant memory
- Consider disabling in production for high-volume applications
??? example "History Item Structure"
```python
{
'type': 'request' or 'response',
'operation': 'GetDeviceInformation',
'xml': '<soap:Envelope>...</soap:Envelope>',
'http_headers': {'Content-Type': 'text/xml', ...}
}
```
??? note "Notes"
- Plugin is automatically created when `capture_xml=True`
- Captured XML includes SOAP envelope, headers, and body
- HTTP headers are captured as dictionaries
- History preserves chronological order
- Pretty printing uses lxml for reliable formatting
- All captured data is stored in memory
??? note "See Also"
- `zeep.Plugin`: Base class for zeep plugins
- [`ONVIFClient`](../core/onvif_client.md): Client that uses this plugin
- `lxml.etree`: XML processing library
"""
def __init__(self, pretty_print=True):
"""Initialize XML capture plugin.
Args:
pretty_print (bool): If True, format XML with indentation
"""
self.pretty_print = pretty_print
self.last_sent_xml = None
self.last_received_xml = None
self.last_operation = None
self.history = [] # Store all requests/responses
logger.debug("XMLCapturePlugin initialized (pretty_print=%s)", pretty_print)
def _format_xml(self, element) -> str:
"""Format XML element with proper indentation using lxml.
Args:
element: lxml Element to format
Returns:
Pretty-printed XML string
"""
try:
# Convert element to string first
xml_bytes = etree.tostring(element, encoding="utf-8")
# Re-parse with parser that removes blank text
# This is safe as we control the input (it's from zeep)
parser = etree.XMLParser(
remove_blank_text=True, resolve_entities=False, no_network=True
)
reparsed = etree.fromstring(xml_bytes, parser)
# Now pretty print the cleaned tree
xml_string = etree.tostring(
reparsed, pretty_print=True, encoding="unicode", xml_declaration=False
)
logger.debug("XML formatted successfully")
return xml_string.strip()
except Exception as e: # pylint: disable=broad-except
# Fallback to non-pretty printed version
logger.warning("XML formatting failed, using fallback: %s", e)
return etree.tostring(element, pretty_print=False, encoding="unicode")
def egress(
self,
envelope: etree._Element,
http_headers: dict[str, str],
operation: Any,
binding_options: dict[str, object],
):
"""Capture a SOAP request before it is sent to the device.
This Zeep ``egress`` hook serializes the outgoing SOAP envelope and stores
it in `last_sent_xml`. The request is also appended to `history` together
with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this
hook only observes the outgoing request and does not modify it.
Args:
envelope: SOAP request envelope that is about to be sent.
http_headers: HTTP request headers that will be sent with the request.
operation: Zeep operation being invoked.
binding_options: Zeep binding options for the current request.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
"""
logger.debug(
"Capturing outgoing SOAP request for operation: %s", operation.name
)
# Serialize XML with proper pretty printing
if self.pretty_print:
self.last_sent_xml = self._format_xml(envelope)
else:
self.last_sent_xml = etree.tostring(
envelope, pretty_print=False, encoding="unicode"
)
self.last_operation = operation.name
# Store in history
self.history.append(
{
"type": "request",
"operation": operation.name,
"xml": self.last_sent_xml,
"http_headers": dict(http_headers) if http_headers else {},
}
)
logger.debug(
"Captured SOAP request for %s (%d chars)",
operation.name,
len(self.last_sent_xml),
)
return envelope, http_headers
def ingress(
self, envelope: etree._Element, http_headers: dict[str, str], operation: Any
):
"""Capture a SOAP response after it is received from the device.
This Zeep ``ingress`` hook serializes the incoming SOAP envelope and stores
it in `last_received_xml`. The response is also appended to `history`
together with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this
hook only observes the incoming response and does not modify it.
Args:
envelope: Raw SOAP response envelope received from the device.
http_headers: HTTP response headers returned by the device.
operation: Zeep operation associated with the response.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
"""
logger.debug(
"Capturing incoming SOAP response for operation: %s", operation.name
)
# Serialize XML with proper pretty printing
if self.pretty_print:
self.last_received_xml = self._format_xml(envelope)
else:
self.last_received_xml = etree.tostring(
envelope, pretty_print=False, encoding="unicode"
)
# Store in history
self.history.append(
{
"type": "response",
"operation": operation.name,
"xml": self.last_received_xml,
"http_headers": dict(http_headers) if http_headers else {},
}
)
logger.debug(
"Captured SOAP response for %s (%d chars)",
operation.name,
len(self.last_received_xml),
)
return envelope, http_headers
def get_last_request(self) -> str | None:
"""Get the last captured request XML.
Returns:
Last captured request XML
"""
return self.last_sent_xml
def get_last_response(self) -> str | None:
"""Get the last captured response XML.
Returns:
Last captured response XML
"""
return self.last_received_xml
def get_history(self) -> list:
"""Get all captured requests and responses.
Returns:
List of all captured requests and responses.
"""
return self.history
def clear_history(self) -> None:
"""Clear the capture history."""
history_count = len(self.history)
self.history = []
self.last_sent_xml = None
self.last_received_xml = None
self.last_operation = None
logger.debug("Cleared XML capture history (%d items)", history_count)
def save_to_file(self, request_file=None, response_file=None) -> None:
"""Save captured XML to files.
Args:
request_file (str): Path to save request XML
response_file (str): Path to save response XML
"""
if request_file and self.last_sent_xml:
try:
with open(request_file, "w", encoding="utf-8") as f:
f.write(self.last_sent_xml)
logger.info("Saved SOAP request XML to: %s", request_file)
except OSError as e:
logger.error("Failed to save request XML to %s: %s", request_file, e)
if response_file and self.last_received_xml:
try:
with open(response_file, "w", encoding="utf-8") as f:
f.write(self.last_received_xml)
logger.info("Saved SOAP response XML to: %s", response_file)
except OSError as e:
logger.error("Failed to save response XML to %s: %s", response_file, e)
__init__(pretty_print=True)
Initialize XML capture plugin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pretty_print
|
bool
|
If True, format XML with indentation |
True
|
Source code in onvif\utils\plugins.py
def __init__(self, pretty_print=True):
"""Initialize XML capture plugin.
Args:
pretty_print (bool): If True, format XML with indentation
"""
self.pretty_print = pretty_print
self.last_sent_xml = None
self.last_received_xml = None
self.last_operation = None
self.history = [] # Store all requests/responses
logger.debug("XMLCapturePlugin initialized (pretty_print=%s)", pretty_print)
egress(envelope: etree._Element, http_headers: dict[str, str], operation: Any, binding_options: dict[str, object])
Capture a SOAP request before it is sent to the device.
This Zeep egress hook serializes the outgoing SOAP envelope and stores
it in last_sent_xml. The request is also appended to history together
with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this hook only observes the outgoing request and does not modify it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
_Element
|
SOAP request envelope that is about to be sent. |
required |
http_headers
|
dict[str, str]
|
HTTP request headers that will be sent with the request. |
required |
operation
|
Any
|
Zeep operation being invoked. |
required |
binding_options
|
dict[str, object]
|
Zeep binding options for the current request. |
required |
Returns:
| Type | Description |
|---|---|
|
A tuple containing the unchanged |
Source code in onvif\utils\plugins.py
def egress(
self,
envelope: etree._Element,
http_headers: dict[str, str],
operation: Any,
binding_options: dict[str, object],
):
"""Capture a SOAP request before it is sent to the device.
This Zeep ``egress`` hook serializes the outgoing SOAP envelope and stores
it in `last_sent_xml`. The request is also appended to `history` together
with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this
hook only observes the outgoing request and does not modify it.
Args:
envelope: SOAP request envelope that is about to be sent.
http_headers: HTTP request headers that will be sent with the request.
operation: Zeep operation being invoked.
binding_options: Zeep binding options for the current request.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
"""
logger.debug(
"Capturing outgoing SOAP request for operation: %s", operation.name
)
# Serialize XML with proper pretty printing
if self.pretty_print:
self.last_sent_xml = self._format_xml(envelope)
else:
self.last_sent_xml = etree.tostring(
envelope, pretty_print=False, encoding="unicode"
)
self.last_operation = operation.name
# Store in history
self.history.append(
{
"type": "request",
"operation": operation.name,
"xml": self.last_sent_xml,
"http_headers": dict(http_headers) if http_headers else {},
}
)
logger.debug(
"Captured SOAP request for %s (%d chars)",
operation.name,
len(self.last_sent_xml),
)
return envelope, http_headers
ingress(envelope: etree._Element, http_headers: dict[str, str], operation: Any)
Capture a SOAP response after it is received from the device.
This Zeep ingress hook serializes the incoming SOAP envelope and stores
it in last_received_xml. The response is also appended to history
together with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this hook only observes the incoming response and does not modify it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
_Element
|
Raw SOAP response envelope received from the device. |
required |
http_headers
|
dict[str, str]
|
HTTP response headers returned by the device. |
required |
operation
|
Any
|
Zeep operation associated with the response. |
required |
Returns:
| Type | Description |
|---|---|
|
A tuple containing the unchanged |
Source code in onvif\utils\plugins.py
def ingress(
self, envelope: etree._Element, http_headers: dict[str, str], operation: Any
):
"""Capture a SOAP response after it is received from the device.
This Zeep ``ingress`` hook serializes the incoming SOAP envelope and stores
it in `last_received_xml`. The response is also appended to `history`
together with the operation name and HTTP headers.
The original envelope and HTTP headers are returned unchanged, so this
hook only observes the incoming response and does not modify it.
Args:
envelope: Raw SOAP response envelope received from the device.
http_headers: HTTP response headers returned by the device.
operation: Zeep operation associated with the response.
Returns:
A tuple containing the unchanged ``envelope`` and ``http_headers``.
"""
logger.debug(
"Capturing incoming SOAP response for operation: %s", operation.name
)
# Serialize XML with proper pretty printing
if self.pretty_print:
self.last_received_xml = self._format_xml(envelope)
else:
self.last_received_xml = etree.tostring(
envelope, pretty_print=False, encoding="unicode"
)
# Store in history
self.history.append(
{
"type": "response",
"operation": operation.name,
"xml": self.last_received_xml,
"http_headers": dict(http_headers) if http_headers else {},
}
)
logger.debug(
"Captured SOAP response for %s (%d chars)",
operation.name,
len(self.last_received_xml),
)
return envelope, http_headers
get_last_request() -> str | None
get_last_response() -> str | None
get_history() -> list
Get all captured requests and responses.
Returns:
| Type | Description |
|---|---|
list
|
List of all captured requests and responses. |
clear_history() -> None
Clear the capture history.
Source code in onvif\utils\plugins.py
save_to_file(request_file=None, response_file=None) -> None
Save captured XML to files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request_file
|
str
|
Path to save request XML |
None
|
response_file
|
str
|
Path to save response XML |
None
|
Source code in onvif\utils\plugins.py
def save_to_file(self, request_file=None, response_file=None) -> None:
"""Save captured XML to files.
Args:
request_file (str): Path to save request XML
response_file (str): Path to save response XML
"""
if request_file and self.last_sent_xml:
try:
with open(request_file, "w", encoding="utf-8") as f:
f.write(self.last_sent_xml)
logger.info("Saved SOAP request XML to: %s", request_file)
except OSError as e:
logger.error("Failed to save request XML to %s: %s", request_file, e)
if response_file and self.last_received_xml:
try:
with open(response_file, "w", encoding="utf-8") as f:
f.write(self.last_received_xml)
logger.info("Saved SOAP response XML to: %s", response_file)
except OSError as e:
logger.error("Failed to save response XML to %s: %s", response_file, e)
ReferenceParametersPlugin
Bases: Plugin
Zeep plugin for injecting WS-Addressing reference parameters.
The plugin adds WS-Addressing reference parameters to the SOAP Header
of outgoing requests. It is primarily useful for ONVIF services whose
endpoint references contain ReferenceParameters that must be included
in subsequent SOAP requests.
Reference parameters are deep-copied before insertion so the original XML elements can be reused safely across requests.
Attributes:
| Name | Type | Description |
|---|---|---|
reference_parameters |
XML elements to inject into outgoing SOAP headers. |
Version History
- Available since
>=v0.3.1.
Note
The plugin supports both SOAP 1.1 and SOAP 1.2 envelope namespaces.
Source code in onvif\utils\plugins.py
class ReferenceParametersPlugin(Plugin):
"""Zeep plugin for injecting WS-Addressing reference parameters.
The plugin adds WS-Addressing reference parameters to the SOAP ``Header``
of outgoing requests. It is primarily useful for ONVIF services whose
endpoint references contain ``ReferenceParameters`` that must be included
in subsequent SOAP requests.
Reference parameters are deep-copied before insertion so the original
XML elements can be reused safely across requests.
Attributes:
reference_parameters: XML elements to inject into outgoing SOAP
headers.
!!! tip "Version History"
- Available since [`>=v0.3.1`](/onvif-python/releases/#v0.3.1).
!!! note
The plugin supports both SOAP 1.1 and SOAP 1.2 envelope namespaces.
"""
SOAP_NAMESPACES = (
"http://schemas.xmlsoap.org/soap/envelope/",
"http://www.w3.org/2003/05/soap-envelope",
)
def __init__(self, reference_parameters: list[etree._Element] | None = None):
self.reference_parameters = reference_parameters or []
def egress(
self,
envelope: etree._Element,
http_headers: dict[str, str],
operation: Any,
binding_options: dict[str, object],
):
"""Inject WS-Addressing reference parameters into a SOAP request.
This Zeep ``egress`` hook adds the configured reference parameters to the
SOAP ``Header`` before the request is sent to the device.
If the SOAP envelope does not contain a ``Header`` element, one is created.
Reference parameters are deep-copied before being inserted so the original
XML elements remain unchanged and can safely be reused.
Args:
envelope: SOAP request envelope that is about to be sent.
http_headers: HTTP request headers that will be sent with the request.
operation: Zeep operation being invoked.
binding_options: Zeep binding options for the current request.
Returns:
A tuple containing the modified ``envelope`` and the unchanged ``http_headers``.
Raises:
RuntimeError: If the SOAP envelope uses an unsupported SOAP namespace.
"""
soap_namespace = etree.QName(envelope).namespace
if soap_namespace not in self.SOAP_NAMESPACES:
raise RuntimeError(f"Unsupported SOAP envelope namespace: {soap_namespace}")
header = envelope.find(f"{{{soap_namespace}}}Header")
if header is None:
header = etree.Element(f"{{{soap_namespace}}}Header")
envelope.insert(0, header)
for parameter in self.reference_parameters:
header.append(deepcopy(parameter))
return envelope, http_headers
egress(envelope: etree._Element, http_headers: dict[str, str], operation: Any, binding_options: dict[str, object])
Inject WS-Addressing reference parameters into a SOAP request.
This Zeep egress hook adds the configured reference parameters to the
SOAP Header before the request is sent to the device.
If the SOAP envelope does not contain a Header element, one is created.
Reference parameters are deep-copied before being inserted so the original
XML elements remain unchanged and can safely be reused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
_Element
|
SOAP request envelope that is about to be sent. |
required |
http_headers
|
dict[str, str]
|
HTTP request headers that will be sent with the request. |
required |
operation
|
Any
|
Zeep operation being invoked. |
required |
binding_options
|
dict[str, object]
|
Zeep binding options for the current request. |
required |
Returns:
| Type | Description |
|---|---|
|
A tuple containing the modified |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the SOAP envelope uses an unsupported SOAP namespace. |
Source code in onvif\utils\plugins.py
def egress(
self,
envelope: etree._Element,
http_headers: dict[str, str],
operation: Any,
binding_options: dict[str, object],
):
"""Inject WS-Addressing reference parameters into a SOAP request.
This Zeep ``egress`` hook adds the configured reference parameters to the
SOAP ``Header`` before the request is sent to the device.
If the SOAP envelope does not contain a ``Header`` element, one is created.
Reference parameters are deep-copied before being inserted so the original
XML elements remain unchanged and can safely be reused.
Args:
envelope: SOAP request envelope that is about to be sent.
http_headers: HTTP request headers that will be sent with the request.
operation: Zeep operation being invoked.
binding_options: Zeep binding options for the current request.
Returns:
A tuple containing the modified ``envelope`` and the unchanged ``http_headers``.
Raises:
RuntimeError: If the SOAP envelope uses an unsupported SOAP namespace.
"""
soap_namespace = etree.QName(envelope).namespace
if soap_namespace not in self.SOAP_NAMESPACES:
raise RuntimeError(f"Unsupported SOAP envelope namespace: {soap_namespace}")
header = envelope.find(f"{{{soap_namespace}}}Header")
if header is None:
header = etree.Element(f"{{{soap_namespace}}}Header")
envelope.insert(0, header)
for parameter in self.reference_parameters:
header.append(deepcopy(parameter))
return envelope, http_headers