New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

onvif-python

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

onvif-python - pypi Package Compare versions

Comparing version
0.2.3
to
0.2.4
+31
onvif/services/events/pausable_subscription.py
# onvif/services/events/pausable_subscription.py
from ...operator import ONVIFOperator
from ...utils import ONVIFWSDL, ONVIFService
class PausableSubscription(ONVIFService):
def __init__(self, xaddr=None, **kwargs):
# References:
# - PausableSubscriptionManagerBinding (ver10/events/wsdl/event-vs.wsdl)
# - Operations: https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl
definition = ONVIFWSDL.get_definition("pausable_subscription")
self.operator = ONVIFOperator(
definition["path"],
binding=f"{{{definition['namespace']}}}{definition['binding']}",
xaddr=xaddr,
**kwargs,
)
def Renew(self, TerminationTime=None):
return self.operator.call("Renew", TerminationTime=TerminationTime)
def Unsubscribe(self):
return self.operator.call("Unsubscribe")
def PauseSubscription(self):
return self.operator.call("PauseSubscription")
def ResumeSubscription(self):
return self.operator.call("ResumeSubscription")
+11
-23
Metadata-Version: 2.4
Name: onvif-python
Version: 0.2.3
Version: 0.2.4
Summary: A modern Python library for ONVIF-compliant devices

@@ -47,10 +47,9 @@ Author-email: Nirsimetri Technologies® <open@nirsimetri.com>

<div align="center">
[![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)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
[![PyPI](https://img.shields.io/badge/PyPI-0.2.3-orange?logo=archive)](https://pypi.org/project/onvif-python/)
[![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
<img alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy" href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/nirsimetri/onvif-python">
<img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.4-orange?logo=archive&color=yellow" href="https://pypi.org/project/onvif-python/">
<img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red" href="https://pepy.tech/projects/onvif-python">
<br>
[![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)
[![Upload Python Package](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml)
<img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml">
<img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml">
</div>

@@ -360,3 +359,3 @@

ONVIF Terminal Client — v0.2.3
ONVIF Terminal Client — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -434,3 +433,3 @@

```bash
ONVIF Interactive Shell — v0.2.3
ONVIF Interactive Shell — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -1034,3 +1033,3 @@

| Device Management | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [device.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/device/wsdl/devicemgmt.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ⚠️ Partial |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Access Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessControl.xml) | [accesscontrol.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/accesscontrol.wsdl) | [types.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/types.xsd) | ✅ Complete |

@@ -1057,3 +1056,2 @@ | Access Rules | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessRules.xml) | [accessrules.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/accessrules/wsdl/accessrules.wsdl) | - | ✅ Complete |

| Replay Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Replay.xml) | [replay.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/replay.wsdl) | - | ✅ Complete |
| Resource Query | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/ResourceQuery.xml) | - | | ❌ Any idea? |
| Schedule | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Schedule.xml) | [schedule.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schedule/wsdl/schedule.wsdl) | - | ✅ Complete |

@@ -1063,3 +1061,2 @@ | Security | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Security.xml) | [advancedsecurity.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl) | - | ✅ Complete |

| Uplink | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Uplink.xml) | [uplink.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/uplink/wsdl/uplink.wsdl) | - | ✅ Complete |
| WebRTC | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/WebRTC.xml) | - | - | ❌ Any idea? |

@@ -1178,15 +1175,6 @@ </details>

## Alternatives
If you are looking for other ONVIF Python libraries, here are some alternatives:
- [python-onvif-zeep](https://github.com/FalkTannhaeuser/python-onvif-zeep):
A synchronous ONVIF client library for Python, using Zeep for SOAP communication. Focuses on compatibility and ease of use for standard ONVIF device operations. Good for scripts and applications where async is not required.
- [python-onvif-zeep-async](https://github.com/openvideolibs/python-onvif-zeep-async):
An asynchronous ONVIF client library for Python, based on Zeep and asyncio. Suitable for applications requiring non-blocking operations and concurrent device communication. Supports many ONVIF services and is actively maintained.
## References
- [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/)
- [ONVIF Official Specs Repository](https://github.com/onvif/specs)
- [ONVIF Application Programmer's Guide](https://www.onvif.org/wp-content/uploads/2016/12/ONVIF_WG-APG-Application_Programmers_Guide-1.pdf)
- [ONVIF 2.0 Service Operation Index](https://www.onvif.org/onvif/ver20/util/operationIndex.html)

@@ -1193,0 +1181,0 @@ - [Usage Examples](./examples/)

@@ -44,2 +44,3 @@ LICENSE.md

onvif/services/events/notification.py
onvif/services/events/pausable_subscription.py
onvif/services/events/pullpoint.py

@@ -131,3 +132,2 @@ onvif/services/events/subscription.py

tests/test_error_handlers.py
tests/test_exceptions.py
tests/test_wsdl_map.py
tests/test_exceptions.py

@@ -197,3 +197,3 @@ # onvif/cli/interactive.py

"/ /_/ / /| / | |/ // // __/ ",
"\\____/_/ |_/ |___/___/_/ v0.2.3",
"\\____/_/ |_/ |___/___/_/ v0.2.4",
" ",

@@ -1404,3 +1404,3 @@ ]

help_text = f"""
{colorize('ONVIF Interactive Shell — v0.2.3', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
{colorize('ONVIF Interactive Shell — v0.2.4', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}

@@ -1407,0 +1407,0 @@ {colorize('Basic Commands:', 'yellow')}

@@ -24,3 +24,3 @@ # onvif/cli/main.py

prog="onvif",
description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.3\nhttps://github.com/nirsimetri/onvif-python",
description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.4\nhttps://github.com/nirsimetri/onvif-python",
formatter_class=argparse.RawDescriptionHelpFormatter,

@@ -165,286 +165,2 @@ epilog=f"""

def search_products(search_term: str, page: int = 1, per_page: int = 20) -> None:
"""Search ONVIF products database and display results in table format with pagination.
Args:
search_term: Search term to match against model, post_title, and company_name fields
page: Page number (1-based)
per_page: Number of results per page
"""
# Get the database path relative to the script location
current_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(current_dir, "..", "db", "products.db")
db_path = os.path.normpath(db_path)
if not os.path.exists(db_path):
print(f"{colorize('Error:', 'red')} Products database not found at {db_path}")
sys.exit(1)
# Validate pagination parameters
if page < 1:
print(f"{colorize('Error:', 'red')} Page number must be 1 or greater")
sys.exit(1)
if per_page < 1 or per_page > 100:
print(f"{colorize('Error:', 'red')} Per-page must be between 1 and 100")
sys.exit(1)
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# First, get total count for pagination info
count_query = """
SELECT COUNT(*)
FROM onvif_products
WHERE LOWER(model) LIKE LOWER(?)
OR LOWER(post_title) LIKE LOWER(?)
OR LOWER(company_name) LIKE LOWER(?)
OR LOWER(product_category) LIKE LOWER(?)
"""
search_pattern = f"%{search_term}%"
cursor.execute(
count_query,
(search_pattern, search_pattern, search_pattern, search_pattern),
)
total_count = cursor.fetchone()[0]
if total_count == 0:
print(
f"{colorize('No products found matching:', 'yellow')} {colorize(search_term, 'white')}"
)
return
# Calculate pagination
total_pages = (total_count + per_page - 1) // per_page # Ceiling division
if page > total_pages:
print(
f"{colorize('Error:', 'red')} Page {page} does not exist. Total pages: {total_pages}"
)
return
offset = (page - 1) * per_page
# Search query with pagination
query = """
SELECT ID, test_date, post_title, product_firmware_version,
product_profiles, product_category, type,
company_name
FROM onvif_products
WHERE LOWER(model) LIKE LOWER(?)
OR LOWER(post_title) LIKE LOWER(?)
OR LOWER(company_name) LIKE LOWER(?)
OR LOWER(product_category) LIKE LOWER(?)
ORDER BY test_date DESC
LIMIT ? OFFSET ?
"""
cursor.execute(
query,
(
search_pattern,
search_pattern,
search_pattern,
search_pattern,
per_page,
offset,
),
)
results = cursor.fetchall()
# Display results in table format
start_result = offset + 1
end_result = min(offset + per_page, total_count)
print(
f"\n{colorize(f'Found {total_count} product(s) matching:', 'green')} {colorize(search_term, 'white')}"
)
print(
f"{colorize(f'Showing {start_result}-{end_result} of {total_count} results', 'cyan')}"
)
print()
# Table headers
headers = [
"ID",
"Test Date",
"Model",
"Firmware",
"Profiles",
"Category",
"Type",
"Company",
]
# Get terminal width for adaptive formatting
try:
terminal_width = shutil.get_terminal_size().columns
except Exception:
terminal_width = 120 # fallback width
# Calculate minimum column widths
min_col_widths = [max(len(str(header)), 8) for header in headers]
# Calculate actual content widths (without truncation first)
actual_widths = [0] * len(headers)
for row in results:
for i, value in enumerate(row):
if value:
if i == 1: # Date column - calculate formatted date width
str_value = str(value)
if "T" in str_value:
date_part = str_value.split("T")[0]
time_part = str_value.split("T")[1].split(".")[0]
if "+" in time_part:
time_part = time_part.split("+")[0]
elif "Z" in time_part:
time_part = time_part.replace("Z", "")
formatted_date = f"{date_part} {time_part}"
actual_widths[i] = max(
actual_widths[i], len(formatted_date)
)
else:
actual_widths[i] = max(actual_widths[i], len(str_value))
else:
actual_widths[i] = max(actual_widths[i], len(str(value)))
# Combine minimum widths with actual content widths
col_widths = [
max(min_col_widths[i], actual_widths[i]) for i in range(len(headers))
]
# Calculate space needed for separators (3 chars per separator: " | ")
separator_space = (len(headers) - 1) * 3
total_content_width = sum(col_widths)
total_needed_width = total_content_width + separator_space
# If table is too wide for terminal, apply smart truncation
if total_needed_width > terminal_width:
available_width = terminal_width - separator_space
# Priority columns that should not be truncated (ID, Date)
protected_cols = {0, 1} # ID and Date columns
protected_width = sum(col_widths[i] for i in protected_cols)
# Width available for other columns
remaining_width = available_width - protected_width
# Columns that can be truncated
truncatable_cols = [
i for i in range(len(headers)) if i not in protected_cols
]
if remaining_width > 0 and truncatable_cols:
# Calculate proportional allocation for truncatable columns
current_truncatable_width = sum(col_widths[i] for i in truncatable_cols)
for i in truncatable_cols:
if current_truncatable_width > 0:
# Proportional allocation
proportion = col_widths[i] / current_truncatable_width
new_width = int(remaining_width * proportion)
# Ensure minimum width
col_widths[i] = max(new_width, min_col_widths[i])
else:
col_widths[i] = min_col_widths[i]
# Print header
header_line = " | ".join(
header.ljust(col_widths[i]) for i, header in enumerate(headers)
)
print(colorize(header_line, "yellow"))
print(colorize("-" * len(header_line), "white"))
# Print data rows
for row in results:
formatted_row = []
for i, value in enumerate(row):
if value is None:
formatted_value = ""
else:
str_value = str(value)
# Special formatting for date column (index 1)
if i == 1 and value: # Date column
try:
# Handle ISO format with timezone
if "T" in str_value:
# Parse ISO format: 2024-08-15T17:53:12.9154121+08:00
# Extract just the date and time part before timezone
date_part = str_value.split("T")[0]
time_part = str_value.split("T")[1].split(".")[
0
] # Remove microseconds
if "+" in time_part:
time_part = time_part.split("+")[0]
elif "Z" in time_part:
time_part = time_part.replace("Z", "")
formatted_value = f"{date_part} {time_part}"
elif (
len(str_value) == 19 and " " in str_value
): # Already in correct format
formatted_value = str_value
elif len(str_value) == 10: # Just date, add time
formatted_value = f"{str_value} 00:00:00"
else:
# Try to parse common formats
try:
parsed_date = datetime.strptime(
str_value, "%Y-%m-%d %H:%M:%S"
)
formatted_value = parsed_date.strftime(
"%Y-%m-%d %H:%M:%S"
)
except ValueError:
try:
parsed_date = datetime.strptime(
str_value, "%Y-%m-%d"
)
formatted_value = parsed_date.strftime(
"%Y-%m-%d 00:00:00"
)
except ValueError:
formatted_value = (
str_value # Keep original if parsing fails
)
except Exception:
formatted_value = str_value # Keep original if any error
else:
# Apply truncation based on calculated column width
max_width = col_widths[i]
if len(str_value) > max_width:
formatted_value = str_value[: max_width - 3] + "..."
else:
formatted_value = str_value
formatted_row.append(formatted_value.ljust(col_widths[i]))
print(" | ".join(formatted_row))
# Display pagination information
print()
newline = "\n" if total_pages == 1 else ""
print(f"{colorize(f'Page {page} of {total_pages}', 'cyan')} {newline}")
# Show navigation hints
nav_hints = []
if page > 1:
nav_hints.append(f"Previous: --page {page - 1}")
if page < total_pages:
nav_hints.append(f"Next: --page {page + 1}")
if nav_hints:
print(f"{colorize('Navigation:', 'white')} {' | '.join(nav_hints)}\n")
conn.close()
except sqlite3.Error as e:
print(f"{colorize('Database error:', 'red')} {e}")
sys.exit(1)
except Exception as e:
print(f"{colorize('Error:', 'red')} {e}")
sys.exit(1)
def main():

@@ -466,3 +182,3 @@ """Main CLI entry point"""

if args.version:
print(colorize("0.2.3", "yellow"))
print(colorize("0.2.4", "yellow"))
sys.exit(0)

@@ -741,2 +457,286 @@

def search_products(search_term: str, page: int = 1, per_page: int = 20) -> None:
"""Search ONVIF products database and display results in table format with pagination.
Args:
search_term: Search term to match against model, post_title, and company_name fields
page: Page number (1-based)
per_page: Number of results per page
"""
# Get the database path relative to the script location
current_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(current_dir, "..", "db", "products.db")
db_path = os.path.normpath(db_path)
if not os.path.exists(db_path):
print(f"{colorize('Error:', 'red')} Products database not found at {db_path}")
sys.exit(1)
# Validate pagination parameters
if page < 1:
print(f"{colorize('Error:', 'red')} Page number must be 1 or greater")
sys.exit(1)
if per_page < 1 or per_page > 100:
print(f"{colorize('Error:', 'red')} Per-page must be between 1 and 100")
sys.exit(1)
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# First, get total count for pagination info
count_query = """
SELECT COUNT(*)
FROM onvif_products
WHERE LOWER(model) LIKE LOWER(?)
OR LOWER(post_title) LIKE LOWER(?)
OR LOWER(company_name) LIKE LOWER(?)
OR LOWER(product_category) LIKE LOWER(?)
"""
search_pattern = f"%{search_term}%"
cursor.execute(
count_query,
(search_pattern, search_pattern, search_pattern, search_pattern),
)
total_count = cursor.fetchone()[0]
if total_count == 0:
print(
f"{colorize('No products found matching:', 'yellow')} {colorize(search_term, 'white')}"
)
return
# Calculate pagination
total_pages = (total_count + per_page - 1) // per_page # Ceiling division
if page > total_pages:
print(
f"{colorize('Error:', 'red')} Page {page} does not exist. Total pages: {total_pages}"
)
return
offset = (page - 1) * per_page
# Search query with pagination
query = """
SELECT ID, test_date, post_title, product_firmware_version,
product_profiles, product_category, type,
company_name
FROM onvif_products
WHERE LOWER(model) LIKE LOWER(?)
OR LOWER(post_title) LIKE LOWER(?)
OR LOWER(company_name) LIKE LOWER(?)
OR LOWER(product_category) LIKE LOWER(?)
ORDER BY test_date DESC
LIMIT ? OFFSET ?
"""
cursor.execute(
query,
(
search_pattern,
search_pattern,
search_pattern,
search_pattern,
per_page,
offset,
),
)
results = cursor.fetchall()
# Display results in table format
start_result = offset + 1
end_result = min(offset + per_page, total_count)
print(
f"\n{colorize(f'Found {total_count} product(s) matching:', 'green')} {colorize(search_term, 'white')}"
)
print(
f"{colorize(f'Showing {start_result}-{end_result} of {total_count} results', 'cyan')}"
)
print()
# Table headers
headers = [
"ID",
"Test Date",
"Model",
"Firmware",
"Profiles",
"Category",
"Type",
"Company",
]
# Get terminal width for adaptive formatting
try:
terminal_width = shutil.get_terminal_size().columns
except Exception:
terminal_width = 120 # fallback width
# Calculate minimum column widths
min_col_widths = [max(len(str(header)), 8) for header in headers]
# Calculate actual content widths (without truncation first)
actual_widths = [0] * len(headers)
for row in results:
for i, value in enumerate(row):
if value:
if i == 1: # Date column - calculate formatted date width
str_value = str(value)
if "T" in str_value:
date_part = str_value.split("T")[0]
time_part = str_value.split("T")[1].split(".")[0]
if "+" in time_part:
time_part = time_part.split("+")[0]
elif "Z" in time_part:
time_part = time_part.replace("Z", "")
formatted_date = f"{date_part} {time_part}"
actual_widths[i] = max(
actual_widths[i], len(formatted_date)
)
else:
actual_widths[i] = max(actual_widths[i], len(str_value))
else:
actual_widths[i] = max(actual_widths[i], len(str(value)))
# Combine minimum widths with actual content widths
col_widths = [
max(min_col_widths[i], actual_widths[i]) for i in range(len(headers))
]
# Calculate space needed for separators (3 chars per separator: " | ")
separator_space = (len(headers) - 1) * 3
total_content_width = sum(col_widths)
total_needed_width = total_content_width + separator_space
# If table is too wide for terminal, apply smart truncation
if total_needed_width > terminal_width:
available_width = terminal_width - separator_space
# Priority columns that should not be truncated (ID, Date)
protected_cols = {0, 1} # ID and Date columns
protected_width = sum(col_widths[i] for i in protected_cols)
# Width available for other columns
remaining_width = available_width - protected_width
# Columns that can be truncated
truncatable_cols = [
i for i in range(len(headers)) if i not in protected_cols
]
if remaining_width > 0 and truncatable_cols:
# Calculate proportional allocation for truncatable columns
current_truncatable_width = sum(col_widths[i] for i in truncatable_cols)
for i in truncatable_cols:
if current_truncatable_width > 0:
# Proportional allocation
proportion = col_widths[i] / current_truncatable_width
new_width = int(remaining_width * proportion)
# Ensure minimum width
col_widths[i] = max(new_width, min_col_widths[i])
else:
col_widths[i] = min_col_widths[i]
# Print header
header_line = " | ".join(
header.ljust(col_widths[i]) for i, header in enumerate(headers)
)
print(colorize(header_line, "yellow"))
print(colorize("-" * len(header_line), "white"))
# Print data rows
for row in results:
formatted_row = []
for i, value in enumerate(row):
if value is None:
formatted_value = ""
else:
str_value = str(value)
# Special formatting for date column (index 1)
if i == 1 and value: # Date column
try:
# Handle ISO format with timezone
if "T" in str_value:
# Parse ISO format: 2024-08-15T17:53:12.9154121+08:00
# Extract just the date and time part before timezone
date_part = str_value.split("T")[0]
time_part = str_value.split("T")[1].split(".")[
0
] # Remove microseconds
if "+" in time_part:
time_part = time_part.split("+")[0]
elif "Z" in time_part:
time_part = time_part.replace("Z", "")
formatted_value = f"{date_part} {time_part}"
elif (
len(str_value) == 19 and " " in str_value
): # Already in correct format
formatted_value = str_value
elif len(str_value) == 10: # Just date, add time
formatted_value = f"{str_value} 00:00:00"
else:
# Try to parse common formats
try:
parsed_date = datetime.strptime(
str_value, "%Y-%m-%d %H:%M:%S"
)
formatted_value = parsed_date.strftime(
"%Y-%m-%d %H:%M:%S"
)
except ValueError:
try:
parsed_date = datetime.strptime(
str_value, "%Y-%m-%d"
)
formatted_value = parsed_date.strftime(
"%Y-%m-%d 00:00:00"
)
except ValueError:
formatted_value = (
str_value # Keep original if parsing fails
)
except Exception:
formatted_value = str_value # Keep original if any error
else:
# Apply truncation based on calculated column width
max_width = col_widths[i]
if len(str_value) > max_width:
formatted_value = str_value[: max_width - 3] + "..."
else:
formatted_value = str_value
formatted_row.append(formatted_value.ljust(col_widths[i]))
print(" | ".join(formatted_row))
# Display pagination information
print()
newline = "\n" if total_pages == 1 else ""
print(f"{colorize(f'Page {page} of {total_pages}', 'cyan')} {newline}")
# Show navigation hints
nav_hints = []
if page > 1:
nav_hints.append(f"Previous: --page {page - 1}")
if page < total_pages:
nav_hints.append(f"Next: --page {page + 1}")
if nav_hints:
print(f"{colorize('Navigation:', 'white')} {' | '.join(nav_hints)}\n")
conn.close()
except sqlite3.Error as e:
print(f"{colorize('Database error:', 'red')} {e}")
sys.exit(1)
except Exception as e:
print(f"{colorize('Error:', 'red')} {e}")
sys.exit(1)
def setup_warning_format():

@@ -743,0 +743,0 @@ """Setup custom warning format to show clean, concise warnings"""

@@ -72,2 +72,11 @@ # onvif/cli/utils.py

def _is_valid_json(s: str) -> bool:
"""Check if a string is valid JSON without raising exceptions."""
try:
json.loads(s)
except ValueError:
return False
return True
def parse_json_params(params_str: str) -> Dict[str, Any]:

@@ -84,6 +93,4 @@ """Parse parameters from a JSON string or key=value pairs into a dict.

# If the whole string is valid JSON, return it directly
try:
return json.loads(params_str)
except Exception:
pass
if _is_valid_json(params_str):
return json.loads(params_str) # We know it's valid, so this should not fail

@@ -356,5 +363,3 @@ # Otherwise parse key=value pairs but allow JSON values for the right-hand side

return None
def colorize(text: str, color: str) -> str:

@@ -383,4 +388,8 @@ """Add color to text for terminal output"""

)
except Exception:
pass # Fallback to no colors if error
except (ImportError, AttributeError, OSError):
# Specific exceptions that can occur:
# - ImportError: ctypes not available
# - AttributeError: Windows API functions not available
# - OSError: Console mode setting failed
colorize._colors_enabled = False

@@ -917,4 +926,8 @@ colors = {

)
except Exception:
pass # Skip if can't parse
except (etree.XMLSyntaxError, OSError, PermissionError):
# Skip files that can't be parsed or accessed:
# - XMLSyntaxError: malformed XML
# - OSError: file access issues
# - PermissionError: insufficient permissions
continue

@@ -954,4 +967,8 @@ # Process includes

)
except Exception:
pass # Skip if can't parse
except (etree.XMLSyntaxError, OSError, PermissionError):
# Skip files that can't be parsed or accessed:
# - XMLSyntaxError: malformed XML
# - OSError: file access issues
# - PermissionError: insufficient permissions
continue

@@ -1269,4 +1286,6 @@

attr_doc_elem = attr.find("xs:annotation/xs:documentation", namespaces)
if attr_doc_elem is not None and attr_doc_elem.text:
attr_doc = clean_documentation_html(attr_doc_elem.text.strip())
if attr_doc_elem is not None:
full_text = extract_documentation_text(attr_doc_elem)
if full_text:
attr_doc = clean_documentation_html(full_text)

@@ -1302,4 +1321,6 @@ attr_info = {

doc_elem = elem.find("xs:annotation/xs:documentation", namespaces)
if doc_elem is not None and doc_elem.text:
child_doc = clean_documentation_html(doc_elem.text.strip())
if doc_elem is not None:
full_text = extract_documentation_text(doc_elem)
if full_text:
child_doc = clean_documentation_html(full_text)

@@ -1394,4 +1415,6 @@ child_info = {

attr_doc_elem = attr.find("xs:annotation/xs:documentation", namespaces)
if attr_doc_elem is not None and attr_doc_elem.text:
attr_doc = clean_documentation_html(attr_doc_elem.text.strip())
if attr_doc_elem is not None:
full_text = extract_documentation_text(attr_doc_elem)
if full_text:
attr_doc = clean_documentation_html(full_text)

@@ -1444,4 +1467,6 @@ attr_info = {

attr_doc_elem = attr.find("xs:annotation/xs:documentation", namespaces)
if attr_doc_elem is not None and attr_doc_elem.text:
attr_doc = clean_documentation_html(attr_doc_elem.text.strip())
if attr_doc_elem is not None:
full_text = extract_documentation_text(attr_doc_elem)
if full_text:
attr_doc = clean_documentation_html(full_text)

@@ -1477,4 +1502,6 @@ attr_info = {

doc_elem = elem.find("xs:annotation/xs:documentation", namespaces)
if doc_elem is not None and doc_elem.text:
child_doc = clean_documentation_html(doc_elem.text.strip())
if doc_elem is not None:
full_text = extract_documentation_text(doc_elem)
if full_text:
child_doc = clean_documentation_html(full_text)

@@ -1481,0 +1508,0 @@ child_info = {

@@ -13,2 +13,3 @@ # onvif/client.py

Subscription,
PausableSubscription,
Imaging,

@@ -217,2 +218,5 @@ Media,

self._subscriptions = {} # Dictionary for multiple Subscription instances
self._pausable_subscriptions = (
{}
) # Dictionary for multiple PausableSubscription instances

@@ -468,2 +472,26 @@ self._imaging = None

@service
def pausable_subscription(self, SubscriptionRef):
logger.debug("Initializing PausableSubscription service")
xaddr = None
addr_obj = SubscriptionRef["SubscriptionReference"]["Address"]
if isinstance(addr_obj, dict) and "_value_1" in addr_obj:
xaddr = addr_obj["_value_1"]
elif hasattr(addr_obj, "_value_1"):
xaddr = addr_obj._value_1
xaddr = self._rewrite_xaddr_if_needed(xaddr)
if not xaddr:
raise RuntimeError(
"SubscriptionReference.Address missing in subscription response"
)
if xaddr not in self._pausable_subscriptions:
self._pausable_subscriptions[xaddr] = PausableSubscription(
xaddr=xaddr, **self.common_args
)
return self._pausable_subscriptions[xaddr]
# Imaging

@@ -470,0 +498,0 @@

@@ -8,2 +8,3 @@ # onvif/services/__init__.py

from .events.subscription import Subscription
from .events.pausable_subscription import PausableSubscription
from .imaging import Imaging

@@ -47,2 +48,3 @@ from .media import Media

"Subscription",
"PausableSubscription",
"Imaging",

@@ -49,0 +51,0 @@ "Media",

@@ -258,4 +258,6 @@ # onvif/utils/service.py

doc_text = None
except Exception:
pass # Documentation is optional
except Exception as e:
logger.warning(
f"Could not retrieve documentation for {method_name}: {e}"
) # Documentation is optional

@@ -262,0 +264,0 @@ return {

@@ -245,2 +245,16 @@ # onvif/utils/wsdl.py

},
"pausable_subscription": {
"ver10": {
"path": os.path.join(
base_dir,
(
"event-vs.wsdl"
if use_flat
else "ver10/events/wsdl/event-vs.wsdl"
),
),
"binding": "PausableSubscriptionManagerBinding",
"namespace": "http://www.onvif.org/ver10/events/wsdl",
}
},
"accesscontrol": {

@@ -247,0 +261,0 @@ "ver10": {

+11
-23
Metadata-Version: 2.4
Name: onvif-python
Version: 0.2.3
Version: 0.2.4
Summary: A modern Python library for ONVIF-compliant devices

@@ -47,10 +47,9 @@ Author-email: Nirsimetri Technologies® <open@nirsimetri.com>

<div align="center">
[![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)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
[![PyPI](https://img.shields.io/badge/PyPI-0.2.3-orange?logo=archive)](https://pypi.org/project/onvif-python/)
[![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
<img alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy" href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/nirsimetri/onvif-python">
<img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.4-orange?logo=archive&color=yellow" href="https://pypi.org/project/onvif-python/">
<img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red" href="https://pepy.tech/projects/onvif-python">
<br>
[![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)
[![Upload Python Package](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml)
<img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml">
<img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml">
</div>

@@ -360,3 +359,3 @@

ONVIF Terminal Client — v0.2.3
ONVIF Terminal Client — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -434,3 +433,3 @@

```bash
ONVIF Interactive Shell — v0.2.3
ONVIF Interactive Shell — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -1034,3 +1033,3 @@

| Device Management | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [device.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/device/wsdl/devicemgmt.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ⚠️ Partial |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Access Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessControl.xml) | [accesscontrol.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/accesscontrol.wsdl) | [types.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/types.xsd) | ✅ Complete |

@@ -1057,3 +1056,2 @@ | Access Rules | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessRules.xml) | [accessrules.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/accessrules/wsdl/accessrules.wsdl) | - | ✅ Complete |

| Replay Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Replay.xml) | [replay.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/replay.wsdl) | - | ✅ Complete |
| Resource Query | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/ResourceQuery.xml) | - | | ❌ Any idea? |
| Schedule | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Schedule.xml) | [schedule.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schedule/wsdl/schedule.wsdl) | - | ✅ Complete |

@@ -1063,3 +1061,2 @@ | Security | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Security.xml) | [advancedsecurity.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl) | - | ✅ Complete |

| Uplink | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Uplink.xml) | [uplink.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/uplink/wsdl/uplink.wsdl) | - | ✅ Complete |
| WebRTC | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/WebRTC.xml) | - | - | ❌ Any idea? |

@@ -1178,15 +1175,6 @@ </details>

## Alternatives
If you are looking for other ONVIF Python libraries, here are some alternatives:
- [python-onvif-zeep](https://github.com/FalkTannhaeuser/python-onvif-zeep):
A synchronous ONVIF client library for Python, using Zeep for SOAP communication. Focuses on compatibility and ease of use for standard ONVIF device operations. Good for scripts and applications where async is not required.
- [python-onvif-zeep-async](https://github.com/openvideolibs/python-onvif-zeep-async):
An asynchronous ONVIF client library for Python, based on Zeep and asyncio. Suitable for applications requiring non-blocking operations and concurrent device communication. Supports many ONVIF services and is actively maintained.
## References
- [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/)
- [ONVIF Official Specs Repository](https://github.com/onvif/specs)
- [ONVIF Application Programmer's Guide](https://www.onvif.org/wp-content/uploads/2016/12/ONVIF_WG-APG-Application_Programmers_Guide-1.pdf)
- [ONVIF 2.0 Service Operation Index](https://www.onvif.org/onvif/ver20/util/operationIndex.html)

@@ -1193,0 +1181,0 @@ - [Usage Examples](./examples/)

@@ -7,3 +7,3 @@ [build-system]

name = "onvif-python"
version = "0.2.3"
version = "0.2.4"
description = "A modern Python library for ONVIF-compliant devices"

@@ -10,0 +10,0 @@ readme = "README.md"

+10
-22
<h1 align="center">ONVIF Python</h1>
<div align="center">
[![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)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
[![PyPI](https://img.shields.io/badge/PyPI-0.2.3-orange?logo=archive)](https://pypi.org/project/onvif-python/)
[![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
<img alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy" href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/nirsimetri/onvif-python">
<img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.4-orange?logo=archive&color=yellow" href="https://pypi.org/project/onvif-python/">
<img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red" href="https://pepy.tech/projects/onvif-python">
<br>
[![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)
[![Upload Python Package](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml)
<img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml">
<img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml">
</div>

@@ -316,3 +315,3 @@

ONVIF Terminal Client — v0.2.3
ONVIF Terminal Client — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -390,3 +389,3 @@

```bash
ONVIF Interactive Shell — v0.2.3
ONVIF Interactive Shell — v0.2.4
https://github.com/nirsimetri/onvif-python

@@ -990,3 +989,3 @@

| Device Management | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [device.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/device/wsdl/devicemgmt.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ⚠️ Partial |
| Events | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Core.xml) | [event.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/events/wsdl/event.wsdl) | [onvif.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/onvif.xsd) <br> [common.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schema/common.xsd) | ✅ Complete |
| Access Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessControl.xml) | [accesscontrol.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/accesscontrol.wsdl) | [types.xsd](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/pacs/types.xsd) | ✅ Complete |

@@ -1013,3 +1012,2 @@ | Access Rules | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/AccessRules.xml) | [accessrules.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/accessrules/wsdl/accessrules.wsdl) | - | ✅ Complete |

| Replay Control | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Replay.xml) | [replay.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/replay.wsdl) | - | ✅ Complete |
| Resource Query | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/ResourceQuery.xml) | - | | ❌ Any idea? |
| Schedule | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Schedule.xml) | [schedule.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/schedule/wsdl/schedule.wsdl) | - | ✅ Complete |

@@ -1019,3 +1017,2 @@ | Security | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Security.xml) | [advancedsecurity.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl) | - | ✅ Complete |

| Uplink | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/Uplink.xml) | [uplink.wsdl](https://developer.onvif.org/pub/specs/branches/development/wsdl/ver10/uplink/wsdl/uplink.wsdl) | - | ✅ Complete |
| WebRTC | [Document](https://developer.onvif.org/pub/specs/branches/development/doc/WebRTC.xml) | - | - | ❌ Any idea? |

@@ -1134,15 +1131,6 @@ </details>

## Alternatives
If you are looking for other ONVIF Python libraries, here are some alternatives:
- [python-onvif-zeep](https://github.com/FalkTannhaeuser/python-onvif-zeep):
A synchronous ONVIF client library for Python, using Zeep for SOAP communication. Focuses on compatibility and ease of use for standard ONVIF device operations. Good for scripts and applications where async is not required.
- [python-onvif-zeep-async](https://github.com/openvideolibs/python-onvif-zeep-async):
An asynchronous ONVIF client library for Python, based on Zeep and asyncio. Suitable for applications requiring non-blocking operations and concurrent device communication. Supports many ONVIF services and is actively maintained.
## References
- [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/)
- [ONVIF Official Specs Repository](https://github.com/onvif/specs)
- [ONVIF Application Programmer's Guide](https://www.onvif.org/wp-content/uploads/2016/12/ONVIF_WG-APG-Application_Programmers_Guide-1.pdf)
- [ONVIF 2.0 Service Operation Index](https://www.onvif.org/onvif/ver20/util/operationIndex.html)

@@ -1149,0 +1137,0 @@ - [Usage Examples](./examples/)

import os
import pytest
from onvif import ONVIFWSDL
def test_get_wsdl_definition_valid():
"""Test valid WSDL definition retrieval"""
definition = ONVIFWSDL.get_definition("devicemgmt", "ver10")
assert isinstance(definition, dict)
assert "path" in definition
assert "binding" in definition
assert "namespace" in definition
assert os.path.exists(
definition["path"]
), f"WSDL path not found: {definition['path']}"
def test_get_wsdl_definition_invalid_service():
"""Test invalid service name"""
with pytest.raises(ValueError) as excinfo:
ONVIFWSDL.get_definition("invalid_service", "ver10")
assert "Unknown service" in str(excinfo.value)
def test_get_wsdl_definition_invalid_version():
"""Test invalid version for existing service"""
with pytest.raises(ValueError) as excinfo:
ONVIFWSDL.get_definition("devicemgmt", "ver99")
assert "not available" in str(excinfo.value)
@pytest.mark.parametrize("service", ["devicemgmt", "media"])
@pytest.mark.parametrize("version", ["ver10"])
def test_get_wsdl_definition_combinations(service, version):
"""Test various service and version combinations"""
definition = ONVIFWSDL.get_definition(service, version)
assert isinstance(definition, dict)
assert "path" in definition
assert os.path.exists(
definition["path"]
), f"{service} {version} missing: {definition['path']}"
# Verify binding and namespace are present
assert definition["binding"] is not None
assert definition["namespace"] is not None

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display