Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for new download method in Protect 4.x #249

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ep1cman
Copy link
Collaborator

@ep1cman ep1cman commented Oct 27, 2024

Description of change

Adds export_camera_video() which is the exact implementation of what get_camera_video() used to be.
Adds prepare_camera_video() which is the new implementation used by Unifi Protect 4, and the recommended way to get videos.
Adds download_camera_video() which is the follow call that needs to be made after prepare_camera_video()
Rewrites get_camera_video() to automatically choose between export or prepare/download to maintain backwards compatability.

Once we are happy with the functionality, I will add tests & tidy up the commit history with proper messages.

Pull-Request Checklist

  • Code is up-to-date with the main branch
  • This pull request follows the contributing guidelines.
  • This pull request links relevant issues as Fixes #0000
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change
  • The new commits follow conventions outlined in the conventional commit spec, such as "fix(api): prevent racing of requests".
  • If pre-commit.ci is failing, try pre-commit run -a for further information.
  • If CI / test is failing, try poetry run pytest for further information.

Summary by CodeRabbit

  • New Features

    • Introduced methods for downloading and preparing camera videos, enhancing video handling capabilities.
    • Added a new enumeration for video export types, including options for timelapse and rotating formats.
  • Bug Fixes

    • Improved error handling for camera ID validation and video export types.

Copy link
Contributor

coderabbitai bot commented Oct 27, 2024

Walkthrough

The changes introduce new methods for video handling in the src/uiprotect/api.py file, specifically for downloading and preparing camera videos. The download_camera_video and prepare_camera_video methods have been added, enhancing the functionality of video exports while ensuring backward compatibility. The get_camera_video method has been updated to utilize the new methods. Additionally, a new enumeration class VideoExportType is created in src/uiprotect/data/types.py to categorize video export types, specifically TIMELAPSE and ROTATING.

Changes

File Change Summary
src/uiprotect/api.py - Added download_camera_video method for MP4 video downloading.
- Added prepare_camera_video method for preparing videos over a specified time range.
- Updated get_camera_video to use new methods with improved error handling.
src/uiprotect/data/types.py - Added VideoExportType enum with values TIMELAPSE and ROTATING.
src/uiprotect/data/__init__.py - Added VideoExportType to the __all__ list to make it part of the public API.

Poem

In the land of cameras bright,
New methods spring forth, what a sight!
Download and prepare with ease,
Timelapse videos, sure to please.
With enums to guide, we hop and play,
Celebrating changes, hip-hip-hooray! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
src/uiprotect/api.py (2)

1507-1507: Typo in error message: "support" should be "supported"

In the error message, correct "This method is only support" to "This method is only supported".

Apply this diff to fix the typo:

-                    "This method is only support from Unifi Protect version >= 4.0.0."
+                    "This method is only supported from Unifi Protect version >= 4.0.0."

1583-1583: Typo in docstring: "wtih" should be "with"

In the docstring, fix the typo "wtih" to "with".

Apply this diff:

-            Providing the `fps` parameter creates a "timelapse" export wtih the given FPS
+            Providing the `fps` parameter creates a "timelapse" export with the given FPS
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 38533e0 and 7f8c073.

📒 Files selected for processing (2)
  • src/uiprotect/api.py (6 hunks)
  • src/uiprotect/data/types.py (1 hunks)
🔇 Additional comments (2)
src/uiprotect/data/types.py (1)

623-627: Consider documenting the distinction between VideoExportType and RecordingType

The new VideoExportType enum appears to duplicate values from the existing RecordingType enum:

  • VideoExportType.TIMELAPSE vs RecordingType.TIMELAPSE
  • VideoExportType.ROTATING vs RecordingType.CONTINUOUS (note: uses "rotating" value)

Please add a docstring to clarify:

  1. The specific purpose of VideoExportType
  2. How it differs from RecordingType
  3. Why we need separate enums for seemingly similar concepts

This will help future maintainers understand the distinction between these types.

Let's verify the usage context of both enums:

src/uiprotect/api.py (1)

1532-1532: Verify the use of timeout=0 in the request

Setting timeout=0 disables the timeout for the request, which may cause the client to hang indefinitely if the server becomes unresponsive. Ensure that this behavior is intentional and won't lead to unwanted consequences.

If disabling the timeout is necessary due to potentially long download times, consider adding a comment explaining the reasoning behind this choice for future maintainability.

src/uiprotect/api.py Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
@ep1cman
Copy link
Collaborator Author

ep1cman commented Oct 27, 2024

I forgot to run mypy through this so please bare with me

@bdraco bdraco changed the title New download feat: add support for new download method Oct 27, 2024
@bdraco bdraco changed the title feat: add support for new download method feat: add support for new download method in Protect 4.x Oct 27, 2024
path = "video/prepare"

return await self.api_request(
path,
params=params,
raise_exception=True,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
path = "video/prepare"
return await self.api_request(
path,
params=params,
raise_exception=True,
)
return await self.api_request(
"video/prepare",
params=params,
raise_exception=True,
)

Unnecessary assignment to temp variable

Copy link

codecov bot commented Oct 27, 2024

Codecov Report

Attention: Patch coverage is 27.77778% with 39 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/uiprotect/api.py 22.00% 39 Missing ⚠️
Files with missing lines Coverage Δ
src/uiprotect/data/__init__.py 100.00% <ø> (ø)
src/uiprotect/data/types.py 99.60% <100.00%> (+<0.01%) ⬆️
src/uiprotect/api.py 53.17% <22.00%> (-2.17%) ⬇️

src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved

r = await self.request(
"get",
f"{self.api_path}{path}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"{self.api_path}{path}",
f"{self.api_path}video/download",

Comment on lines 1514 to 1515
path = "video/download"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
path = "video/download"

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (1)
src/uiprotect/api.py (1)

1612-1624: Adjust Lens Parameter Handling for Clarity

In the prepare_camera_video method, the handling of channel_index when it equals 3 is a bit opaque. It's setting the lens parameter to 2, but this magic number may confuse future developers. Consider defining a constant or adding a comment to clarify this behavior.

Add a comment or define a constant:

 if channel_index == 3:
+    # For channel_index 3, set lens parameter to 2 (e.g., package camera lens)
     params.update({"lens": 2})
 else:
     params.update({"channel": channel_index})
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 1612-1614: src/uiprotect/api.py#L1612-L1614
Added lines #L1612 - L1614 were not covered by tests


[warning] 1616-1616: src/uiprotect/api.py#L1616
Added line #L1616 was not covered by tests


[warning] 1618-1621: src/uiprotect/api.py#L1618-L1621
Added lines #L1618 - L1621 were not covered by tests


[warning] 1623-1623: src/uiprotect/api.py#L1623
Added line #L1623 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7f8c073 and b1858d2.

📒 Files selected for processing (2)
  • src/uiprotect/api.py (5 hunks)
  • src/uiprotect/data/init.py (2 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
src/uiprotect/api.py

[warning] 1509-1510: src/uiprotect/api.py#L1509-L1510
Added lines #L1509 - L1510 were not covered by tests


[warning] 1514-1514: src/uiprotect/api.py#L1514
Added line #L1514 was not covered by tests


[warning] 1516-1516: src/uiprotect/api.py#L1516
Added line #L1516 was not covered by tests


[warning] 1521-1521: src/uiprotect/api.py#L1521
Added line #L1521 was not covered by tests


[warning] 1526-1526: src/uiprotect/api.py#L1526
Added line #L1526 was not covered by tests


[warning] 1532-1532: src/uiprotect/api.py#L1532
Added line #L1532 was not covered by tests


[warning] 1539-1540: src/uiprotect/api.py#L1539-L1540
Added lines #L1539 - L1540 were not covered by tests


[warning] 1542-1546: src/uiprotect/api.py#L1542-L1546
Added lines #L1542 - L1546 were not covered by tests


[warning] 1548-1548: src/uiprotect/api.py#L1548
Added line #L1548 was not covered by tests


[warning] 1550-1550: src/uiprotect/api.py#L1550
Added line #L1550 was not covered by tests


[warning] 1556-1557: src/uiprotect/api.py#L1556-L1557
Added lines #L1556 - L1557 were not covered by tests


[warning] 1562-1562: src/uiprotect/api.py#L1562
Added line #L1562 was not covered by tests


[warning] 1566-1567: src/uiprotect/api.py#L1566-L1567
Added lines #L1566 - L1567 were not covered by tests


[warning] 1593-1594: src/uiprotect/api.py#L1593-L1594
Added lines #L1593 - L1594 were not covered by tests


[warning] 1598-1599: src/uiprotect/api.py#L1598-L1599
Added lines #L1598 - L1599 were not covered by tests


[warning] 1601-1601: src/uiprotect/api.py#L1601
Added line #L1601 was not covered by tests


[warning] 1607-1608: src/uiprotect/api.py#L1607-L1608
Added lines #L1607 - L1608 were not covered by tests


[warning] 1610-1610: src/uiprotect/api.py#L1610
Added line #L1610 was not covered by tests


[warning] 1612-1614: src/uiprotect/api.py#L1612-L1614
Added lines #L1612 - L1614 were not covered by tests


[warning] 1616-1616: src/uiprotect/api.py#L1616
Added line #L1616 was not covered by tests


[warning] 1618-1621: src/uiprotect/api.py#L1618-L1621
Added lines #L1618 - L1621 were not covered by tests


[warning] 1623-1623: src/uiprotect/api.py#L1623
Added line #L1623 was not covered by tests


[warning] 1625-1625: src/uiprotect/api.py#L1625
Added line #L1625 was not covered by tests


[warning] 1627-1627: src/uiprotect/api.py#L1627
Added line #L1627 was not covered by tests


[warning] 1670-1670: src/uiprotect/api.py#L1670
Added line #L1670 was not covered by tests


[warning] 1737-1737: src/uiprotect/api.py#L1737
Added line #L1737 was not covered by tests


[warning] 1747-1748: src/uiprotect/api.py#L1747-L1748
Added lines #L1747 - L1748 were not covered by tests


[warning] 1751-1751: src/uiprotect/api.py#L1751
Added line #L1751 was not covered by tests


[warning] 1753-1753: src/uiprotect/api.py#L1753
Added line #L1753 was not covered by tests

🔇 Additional comments (4)
src/uiprotect/data/__init__.py (3)

69-69: LGTM! Import addition maintains alphabetical order.

The VideoExportType import is correctly placed in alphabetical order within the types import group.


149-149: LGTM! Export addition maintains alphabetical order.

The VideoExportType export is correctly placed in alphabetical order within the all list.


69-69: Verify the VideoExportType implementation.

Let's verify the implementation of VideoExportType enum that's being imported and exported.

Also applies to: 149-149

✅ Verification successful

Let me check the implementation details of VideoExportType by looking at its definition.


VideoExportType implementation is properly defined and used

The VideoExportType is a well-defined enum that:

  • Inherits from str and enum.Enum for string-based enumeration
  • Includes ValuesEnumMixin for additional functionality
  • Has two valid states: TIMELAPSE and ROTATING
  • Is correctly used in api.py for type parameters
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the VideoExportType enum implementation and its usage

# Test 1: Check the enum definition
echo "Checking VideoExportType definition..."
ast-grep --pattern 'class VideoExportType' -A 10

# Test 2: Check for any usage of VideoExportType
echo "Checking VideoExportType usage..."
rg "VideoExportType" --type py

Length of output: 704


Script:

#!/bin/bash
# Get the full implementation of VideoExportType enum
echo "VideoExportType implementation:"
rg -A 10 "class VideoExportType" src/uiprotect/data/types.py

Length of output: 354

src/uiprotect/api.py (1)

1490-1557: Verify Unit Test Coverage for New Methods

The new methods download_camera_video and prepare_camera_video lack unit tests, as indicated by the static analysis hints. To maintain code quality and ensure future reliability, please add unit tests covering these methods.

Would you like assistance in generating unit tests for these methods or should I open a GitHub issue to track this task?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 1509-1510: src/uiprotect/api.py#L1509-L1510
Added lines #L1509 - L1510 were not covered by tests


[warning] 1514-1514: src/uiprotect/api.py#L1514
Added line #L1514 was not covered by tests


[warning] 1516-1516: src/uiprotect/api.py#L1516
Added line #L1516 was not covered by tests


[warning] 1521-1521: src/uiprotect/api.py#L1521
Added line #L1521 was not covered by tests


[warning] 1526-1526: src/uiprotect/api.py#L1526
Added line #L1526 was not covered by tests


[warning] 1532-1532: src/uiprotect/api.py#L1532
Added line #L1532 was not covered by tests


[warning] 1539-1540: src/uiprotect/api.py#L1539-L1540
Added lines #L1539 - L1540 were not covered by tests


[warning] 1542-1546: src/uiprotect/api.py#L1542-L1546
Added lines #L1542 - L1546 were not covered by tests


[warning] 1548-1548: src/uiprotect/api.py#L1548
Added line #L1548 was not covered by tests


[warning] 1550-1550: src/uiprotect/api.py#L1550
Added line #L1550 was not covered by tests


[warning] 1556-1557: src/uiprotect/api.py#L1556-L1557
Added lines #L1556 - L1557 were not covered by tests

src/uiprotect/api.py Show resolved Hide resolved
Comment on lines 1716 to 1728
async def get_camera_video(
self,
camera_id: str,
start: datetime,
end: datetime,
channel_index: int = 0,
validate_channel_id: bool = True,
output_file: Path | None = None,
iterator_callback: IteratorCallback | None = None,
progress_callback: ProgressCallback | None = None,
chunk_size: int = 65536,
fps: int | None = None,
filename: str | None = None,
) -> bytes | None:
"""
Deprecated: maintained for backwards compatibility.

If you are using Unifi Protect 4 or later, please use
prepare_camera_video() and download_camera_video() instead.
"""
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Deprecation Warning for get_camera_video Method

The get_camera_video method is deprecated, but users might not notice this. To help users transition to the new methods, consider adding a DeprecationWarning.

Apply this diff to include a deprecation warning:

 async def get_camera_video(
     self,
     camera_id: str,
     start: datetime,
     end: datetime,
     channel_index: int = 0,
     validate_channel_id: bool = True,
     output_file: Path | None = None,
     iterator_callback: IteratorCallback | None = None,
     progress_callback: ProgressCallback | None = None,
     chunk_size: int = 65536,
     fps: int | None = None,
     filename: str | None = None,
 ) -> bytes | None:
     """
     Deprecated: maintained for backwards compatibility.

     If you are using UniFi Protect 4.0.0 or later, please use
     `prepare_camera_video()` and `download_camera_video()` instead.
     """
+    import warnings
+    warnings.warn(
+        "get_camera_video() is deprecated and will be removed in a future release. "
+        "Use prepare_camera_video() and download_camera_video() instead.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
     if self.bootstrap.nvr.version >= Version("4.0.0"):
         prepare_response = await self.prepare_camera_video(
             camera_id=camera_id,
             start=start,
             end=end,
             channel_index=channel_index,
             validate_channel_id=validate_channel_id,
             fps=fps,
             filename=filename,

Committable suggestion was skipped due to low confidence.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats probably a good idea, but we can't do late imports in async code because it does blocking I/O

@coderabbitai remember we can't do late imports in async code because it does blocking I/O

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bdraco, thanks for the reminder about late imports in async code. To avoid blocking I/O, we can move the import warnings statement to the top of the module. Here's the updated diff:

+import warnings

 async def get_camera_video(
     self,
     camera_id: str,
     start: datetime,
     end: datetime,
     channel_index: int = 0,
     validate_channel_id: bool = True,
     output_file: Path | None = None,
     iterator_callback: IteratorCallback | None = None,
     progress_callback: ProgressCallback | None = None,
     chunk_size: int = 65536,
     fps: int | None = None,
     filename: str | None = None,
 ) -> bytes | None:
     """
     Deprecated: maintained for backwards compatibility.

     If you are using UniFi Protect 4.0.0 or later, please use
     `prepare_camera_video()` and `download_camera_video()` instead.
     """
     warnings.warn(
         "get_camera_video() is deprecated and will be removed in a future release. "
         "Use `prepare_camera_video()` and `download_camera_video()` instead.",
         DeprecationWarning,
         stacklevel=2,
     )
     if self.bootstrap.nvr.version >= Version("4.0.0"):
         prepare_response = await self.prepare_camera_video(
             camera_id=camera_id,
             start=start,
             end=end,
             channel_index=channel_index,
             validate_channel_id=validate_channel_id,
             fps=fps,
             filename=filename,

Let me know if this works for you.


✏️ Learnings added
Learnt from: bdraco
PR: uilibs/uiprotect#249
File: src/uiprotect/api.py:1716-1735
Timestamp: 2024-10-27T22:01:02.393Z
Learning: In async functions in `src/uiprotect/api.py`, avoid late imports because they cause blocking I/O. All import statements should be at the top of the module.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai please produce a committable suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bdraco, certainly! Here's the updated code with the DeprecationWarning:

At the top of src/uiprotect/api.py, add:

import warnings

In the get_camera_video method, add the warning:

warnings.warn(
    "get_camera_video() is deprecated and will be removed in a future release. "
    "Use `prepare_camera_video()` and `download_camera_video()` instead.",
    DeprecationWarning,
    stacklevel=2,
)

The updated method would look like this:

async def get_camera_video(
    self,
    camera_id: str,
    start: datetime,
    end: datetime,
    channel_index: int = 0,
    validate_channel_id: bool = True,
    output_file: Path | None = None,
    iterator_callback: IteratorCallback | None = None,
    progress_callback: ProgressCallback | None = None,
    chunk_size: int = 65536,
    fps: int | None = None,
    filename: str | None = None,
) -> bytes | None:
    """
    Deprecated: maintained for backwards compatibility.

    If you are using UniFi Protect 4.0.0 or later, please use
    `prepare_camera_video()` and `download_camera_video()` instead.
    """
    warnings.warn(
        "get_camera_video() is deprecated and will be removed in a future release. "
        "Use `prepare_camera_video()` and `download_camera_video()` instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    if self.bootstrap.nvr.version >= Version("4.0.0"):
        prepare_response = await self.prepare_camera_video(
            camera_id=camera_id,
            start=start,
            end=end,
            channel_index=channel_index,
            validate_channel_id=validate_channel_id,
            fps=fps,
            filename=filename,
        )
        # Rest of the method...

This will issue a DeprecationWarning when get_camera_video is called, helping users transition to the new methods.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to depreciate this? I just took the code from the old PR, but there is nothing wrong with calling this function per se.

I'm kinda dubious about doing this behind the scene switcheroo in the first place. I don't want to introduce some unexpected bug into downstream dependencies

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

src/uiprotect/api.py Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
src/uiprotect/api.py Outdated Show resolved Hide resolved
@bdraco
Copy link
Member

bdraco commented Oct 27, 2024

Generally looks good. Needs some tests to make sure the http requests are sending the expected payloads

Adds export_camera_video() which is the exact implementation of what get_camera_video() used to be.
Adds prepare_camera_video() which is the new implementation used by Unifi Protect 4, and the recommended way to get videos.
Adds download_camera_video() which is the follow call that needs to be made after prepare_camera_video()
Rewrites get_camera_video() to automatically choose between export or prepare/download to maintain backwards compatability.

Signed-off-by: cyberpower678 <[email protected]>
@ep1cman
Copy link
Collaborator Author

ep1cman commented Oct 27, 2024

I will add some tests in tomorrow as its getting late here now.

I have one remaining question about this whole change. Do we really want to replace the old mechanism with the new one silently based on version? I worry if there is some unexpected differences in this new mechansim it will affect downstream consumers.

@bdraco
Copy link
Member

bdraco commented Oct 27, 2024

I have one remaining question about this whole change. Do we really want to replace the old mechanism with the new one silently based on version? I worry if there is some unexpected differences in this new mechansim it will affect downstream consumers.

Assuming the old methods still work then it makes more sense to add this all as new instead of replacing the old mechanism with the new one silently.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
src/uiprotect/api.py (1)

1555-1563: Enhance error messages for better debugging.

While the validation logic is correct, the error messages could be more descriptive to help identify the specific validation failure.

Apply this diff to improve error messages:

         try:
             camera = self._bootstrap.cameras[camera_id]  # type: ignore[union-attr]
             camera.channels[channel_index]
         except (IndexError, AttributeError, KeyError) as e:
-            raise BadRequest(f"Invalid input: {e}") from e
+            if isinstance(e, KeyError):
+                raise BadRequest(f"Camera not found: {camera_id}") from e
+            elif isinstance(e, IndexError):
+                raise BadRequest(f"Invalid channel index {channel_index} for camera {camera_id}") from e
+            else:
+                raise BadRequest(f"Invalid camera or channel: {e}") from e
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b1858d2 and ed9f594.

📒 Files selected for processing (3)
  • src/uiprotect/api.py (4 hunks)
  • src/uiprotect/data/init.py (2 hunks)
  • src/uiprotect/data/types.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/uiprotect/data/init.py
  • src/uiprotect/data/types.py
🧰 Additional context used
📓 Learnings (1)
src/uiprotect/api.py (1)
Learnt from: bdraco
PR: uilibs/uiprotect#249
File: src/uiprotect/api.py:1490-1512
Timestamp: 2024-10-27T22:01:21.967Z
Learning: In the `download_camera_video` method in `src/uiprotect/api.py`, prefer using built-in exceptions like `ValueError` over custom exceptions unless there is a compelling reason.
🔇 Additional comments (4)
src/uiprotect/api.py (4)

92-93: LGTM! Well-defined version constant.

The constant is appropriately defined and documented, clearly indicating the version where the new download mechanism was introduced.


1488-1510: LGTM! Well-structured method signature and version validation.

The method is well-documented and includes proper version validation to ensure compatibility.


1565-1613: LGTM! Well-implemented video preparation method.

The method is well-structured with proper parameter handling and clear documentation.


1662-1664: LGTM! Proper integration of VideoExportType.

The changes correctly implement timelapse export functionality using the VideoExportType enum.

src/uiprotect/api.py Show resolved Hide resolved
src/uiprotect/api.py Show resolved Hide resolved
@ep1cman
Copy link
Collaborator Author

ep1cman commented Oct 28, 2024

I am trying to write tests, but the built in sample data is pre v4.0.0, so I tried to generate my own data and get the following error trying to run one of the existing tests:

uiprotect on  new-download [!] is 🐳 v24.0.9 via ·uiprotect-461Fq3yj-py3.12 
➜ UFP_SAMPLE_DIR=(pwd)/ufp-data pytest tests/test_api.py -k test_get_camera_video
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_benchmark/logger.py:46: PytestBenchmarkWarning: Benchmarks are automatically disabled because xdist plugin is active.Benchmarks cannot be performed reliably in a parallelized environment.
  warner(PytestBenchmarkWarning(text))
Test session starts (platform: darwin, Python 3.12.3, pytest 8.3.3, pytest-sugar 1.0.0)
cachedir: .pytest_cache
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /Users/seb/git/uiprotect
configfile: pyproject.toml
plugins: asyncio-0.24.0, cov-5.0.0, sugar-1.0.0, timeout-2.3.1, benchmark-4.0.0, xdist-3.6.1
asyncio: mode=Mode.STRICT, default_loop_scope=None
initialized: 8/8 workers/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
ready: 2/8 workers/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
collecting: 2/8 workers/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
collecting: 4/8 workers/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
8 workers [1 item]     
scheduling tests via LoadScheduling
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (modu/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
le-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/control.py:888: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")


―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ERROR at setup of test_get_camera_video ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[gw0] darwin -- Python 3.12.3 /Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/bin/python

request = <SubRequest 'protect_client' for <Coroutine test_get_camera_video>>, kwargs = {}, func = <function protect_client_fixture at 0x10785ccc0>
event_loop_fixture_id = 'event_loop', setup = <function _wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.setup at 0x107a1f560>
finalizer = <function _wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.finalizer at 0x107a1f600>

/Users/seb/Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/coverage/inorout.py:519: CoverageWarning: Module uiprotect was previously imported, but not measured (module-not-measured)
  self.warn(msg, slug="module-not-measured")
    @functools.wraps(fixture)
    def _asyncgen_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
        func = _perhaps_rebind_fixture_func(fixture, request.instance)
        event_loop_fixture_id = _get_event_loop_fixture_id_for_async_fixture(
            request, func
        )
        event_loop = request.getfixturevalue(event_loop_fixture_id)
        kwargs.pop(event_loop_fixture_id, None)
        gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request))
    
        async def setup():
            res = await gen_obj.__anext__()  # type: ignore[union-attr]
            return res
    
        def finalizer() -> None:
            """Yield again, to finalize."""
    
            async def async_finalizer() -> None:
                try:
                    await gen_obj.__anext__()  # type: ignore[union-attr]
                except StopAsyncIteration:
                    pass
                else:
                    msg = "Async generator fixture didn't stop."
                    msg += "Yield only once."
                    raise ValueError(msg)
    
            event_loop.run_until_complete(async_finalizer())
    
>       result = event_loop.run_until_complete(setup())

../../Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:343: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.asdf/installs/python/3.12.3/lib/python3.12/asyncio/base_events.py:687: in run_until_complete
    return future.result()
../../Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pytest_asyncio/plugin.py:325: in setup
    res = await gen_obj.__anext__()  # type: ignore[union-attr]
tests/conftest.py:293: in protect_client_fixture
    yield await setup_client(client, SimpleMockWebsocket())
tests/conftest.py:269: in setup_client
    await client.update()
src/uiprotect/api.py:830: in update
    bootstrap = await self.get_bootstrap()
src/uiprotect/api.py:1181: in get_bootstrap
    return Bootstrap.from_unifi_dict(**data, api=self)
src/uiprotect/data/base.py:139: in from_unifi_dict
    return cls(api=api, **data)
src/uiprotect/data/base.py:109: in __init__
    super().__init__(**data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

__pydantic_self__ = Bootstrap()
data = {'access_key': '1730147274365:6663031e01b7a303e40004ae:73db331bd2638bdf5be407cfc66ddb712c5bd93b362bf541f356208956c9105...mgmt_ip': None, 'api': <uiprotect.api.ProtectApiClient object at 0x107a3db50>, 'audio_bitrate': 64000, ...}, ...}, ...}
values = {'access_key': '1730147274365:6663031e01b7a303e40004ae:73db331bd2638bdf5be407cfc66ddb712c5bd93b362bf541f356208956c9105...dio_detects={}, last_smart_detect_event_ids={}, last_smart_audio_detect_event_ids={}, talkback_stream=None), ...}, ...}
fields_set = {'access_key', 'auth_user_id', 'bridges', 'cameras', 'chimes', 'doorlocks', ...}
validation_error = ValidationError(model='Bootstrap', errors=[{'loc': ('nvr', 'is_stats_gathering_enabled'), 'msg': 'field required', 'ty...: 'type_error.none.not_allowed'}, {'loc': ('nvr', 'network'), 'msg': 'field required', 'type': 'value_error.missing'}])

    def __init__(__pydantic_self__, **data: Any) -> None:
        """
        Create a new model by parsing and validating input data from keyword arguments.
    
        Raises ValidationError if the input data cannot be parsed to form a valid model.
        """
        # Uses something other than `self` the first arg to allow "self" as a settable attribute
        values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data)
        if validation_error:
>           raise validation_error
E           pydantic.v1.error_wrappers.ValidationError: 7 validation errors for Bootstrap
E           nvr -> is_stats_gathering_enabled
E             field required (type=value_error.missing)
E           nvr -> hardware_platform
E             field required (type=value_error.missing)
E           nvr -> is_station
E             field required (type=value_error.missing)
E           nvr -> enable_stats_reporting
E             field required (type=value_error.missing)
E           nvr -> is_hardware
E             field required (type=value_error.missing)
E           nvr -> storage_stats -> storage_distribution -> recording_type_distributions -> 3 -> recording_type
E             none is not an allowed value (type=type_error.none.not_allowed)
E           nvr -> network
E             field required (type=value_error.missing)

../../Library/Caches/pypoetry/virtualenvs/uiprotect-461Fq3yj-py3.12/lib/python3.12/site-packages/pydantic/v1/main.py:341: ValidationError
                                                                                                                                              100% ██████████

---------- coverage: platform darwin, python 3.12.3-final-0 ----------
Name                              Stmts   Miss  Cover   Missing
---------------------------------------------------------------
src/uiprotect/__main__.py            14     14     0%   1-24
src/uiprotect/api.py                818    591    28%   155-158, 217, 222-223, 231-232, 239-241, 246, 250, 254-260, 264-272, 292-297, 301-305, 308-312, 316-320, 331-385, 396-417, 423-444, 454-470, 489, 501-512, 516-518, 522-546, 550-566, 570-597, 600-609, 613-647, 651-675, 679, 683-687, 700, 788, 791, 795-798, 803-817, 831-833, 837-845, 849-872, 875-877, 880-889, 898-947, 988-1043, 1085-1120, 1131-1134, 1141-1144, 1155-1156, 1163, 1167-1172, 1185, 1193-1209, 1217, 1225, 1233, 1241, 1252, 1260, 1268, 1276, 1287, 1296-1310, 1318-1319, 1327, 1335, 1343, 1351, 1359, 1370, 1378, 1389, 1397, 1414-1429, 1447-1463, 1476-1486, 1507-1553, 1557-1563, 1589-1621, 1653-1707, 1721-1730, 1747-1757, 1780-1793, 1813-1814, 1821, 1825-1827, 1843, 1858, 1862, 1866, 1870, 1874-1882, 1886, 1890, 1898, 1912-1924, 1932, 1936, 1943-1965, 1969-1976, 2000-2011, 2033-2042, 2061-2069, 2073-2074, 2082, 2086-2092, 2096-2102, 2106, 2113-2114, 2118-2122
src/uiprotect/data/base.py          511    225    56%   141, 145-172, 181, 210, 242-243, 337, 365-374, 383-390, 403-410, 435-475, 484-515, 519-520, 523, 531-534, 555-557, 566, 571, 576, 591-594, 599, 606-608, 611-613, 616-618, 621-623, 626-628, 637-661, 686-700, 710-752, 756, 760-793, 814, 838-840, 849-852, 921, 947-949, 956-961, 972, 976, 980, 984, 988-990, 995, 1000, 1004, 1009-1012, 1016-1023, 1027-1037, 1041-1050, 1063, 1070-1072, 1076-1078
src/uiprotect/data/bootstrap.py     307    190    38%   105, 109-117, 128-144, 213, 231-239, 243, 246, 250, 254-259, 264-273, 278-282, 287-293, 297, 301, 306-310, 313-321, 328-352, 362-372, 385-403, 426-472, 488-515, 526-557, 566-578, 582-603, 607
src/uiprotect/data/convert.py        24     14    42%   49-59, 72-81
src/uiprotect/data/devices.py      1784    845    53%   114, 143, 148, 157-160, 164-172, 177-180, 185-188, 193-198, 203-206, 210-216, 236-250, 279-285, 289-295, 299, 345-347, 432-457, 480-487, 498, 504-511, 515-521, 528-540, 609-612, 617, 626-628, 643, 652-657, 678-683, 687, 749-770, 778-791, 870, 881, 885, 906-910, 1008, 1024, 1059-1093, 1096-1114, 1118-1125, 1129-1131, 1136-1138, 1143-1147, 1152-1155, 1162-1164, 1169-1173, 1180-1182, 1186, 1190-1191, 1201-1203, 1208, 1216, 1224, 1232-1234, 1239-1241, 1246-1250, 1255-1261, 1266, 1274, 1283-1289, 1294-1297, 1302, 1307-1308, 1319, 1331, 1339, 1344, 1349, 1354, 1358, 1363, 1374, 1382, 1387, 1392, 1397, 1401, 1408, 1418, 1423, 1428, 1433, 1437, 1447, 1455, 1460, 1465, 1470, 1474, 1481, 1489, 1494, 1499, 1504, 1508, 1515-1516, 1526-1527, 1534-1539, 1549, 1561, 1569, 1574, 1579, 1584, 1588, 1595, 1603, 1608, 1613, 1618, 1622, 1629, 1637, 1642, 1647, 1652, 1656, 1663, 1671, 1676, 1681, 1686, 1690, 1697, 1705, 1710, 1715, 1720, 1724, 1731, 1739, 1744, 1749, 1754, 1758, 1766, 1774, 1779, 1784, 1789, 1793, 1800, 1808, 1813, 1818, 1823, 1827, 1834, 1842, 1847, 1852, 1857, 1861, 1868, 1874, 1878, 1882-1884, 1888-1890, 1894-1896, 1900-1907, 1911-1913, 1917, 1921, 1927, 1933, 1939, 1943-1950, 1953-1956, 1959-1966, 1969-1971, 1984-1996, 2009-2024, 2052-2061, 2075-2081, 2085-2091, 2095-2107, 2112, 2119-2126, 2130-2142, 2146-2161, 2165-2171, 2175-2181, 2185-2191, 2195-2201, 2205-2211, 2215-2221, 2225, 2229-2245, 2249-2255, 2259-2265, 2269-2275, 2279-2285, 2289-2296, 2300-2309, 2316-2325, 2332-2348, 2355-2374, 2383-2415, 2425-2444, 2448-2459, 2482-2486, 2506-2510, 2514-2524, 2528-2530, 2533-2535, 2538-2540, 2567-2574, 2596, 2612-2618, 2622-2625, 2633-2636, 2640-2643, 2647-2650, 2654-2657, 2661-2664, 2668-2671, 2684, 2689, 2694, 2705-2714, 2784, 2789, 2806-2817, 2822-2824, 2828, 2832-2834, 2838, 2842, 2846, 2850, 2854, 2861, 2867, 2870-2871, 2875-2877, 2881-2883, 2887-2889, 2893-2895, 2899, 2904-2907, 2912-2915, 2920-2923, 2928-2931, 2936-2939, 2943-2954, 2959-2963, 2968-2971, 2975-2986, 2991-2995, 3000-3003, 3007-3018, 3023-3027, 3032-3035, 3040-3046, 3050-3058, 3075, 3084, 3093, 3100-3103, 3108-3114, 3119-3122, 3126-3132, 3136-3139, 3143-3146, 3154, 3165, 3177, 3182-3185, 3199, 3230, 3235, 3240-3242, 3246-3255, 3260-3271, 3275-3284, 3288-3294, 3303, 3307, 3311-3319, 3328-3339
src/uiprotect/data/nvr.py           773    253    67%   92, 100, 114, 122, 126, 148-150, 163, 170-172, 218, 226-230, 237-249, 283, 295, 305-307, 311-314, 318-321, 325-328, 332-335, 339-347, 355-365, 375-385, 394-404, 424-437, 454-462, 466-480, 552-554, 618-648, 652, 656, 701-707, 730-732, 789-796, 801-806, 810, 814, 818, 822, 826, 830, 834-837, 978, 1009, 1013, 1017, 1021, 1026-1028, 1038, 1047, 1054, 1063, 1071-1072, 1093-1096, 1101-1104, 1108-1111, 1116-1119, 1124-1127, 1131-1137, 1145-1148, 1158-1164, 1168, 1171-1182, 1187-1207, 1212-1216, 1220-1227, 1231-1238, 1243-1247, 1252-1256, 1261-1265, 1270-1275, 1280-1284, 1289-1293, 1298, 1310, 1315, 1331, 1339, 1347, 1355, 1358, 1375, 1383, 1391, 1399, 1407, 1415, 1423, 1431, 1439, 1456-1465, 1484, 1493, 1497
src/uiprotect/data/types.py         510     20    96%   45-47, 62-64, 69, 178, 275, 286, 291, 333, 605, 720-723, 728-731
src/uiprotect/data/user.py          148     44    70%   26, 47, 51-55, 98-106, 110, 153-154, 181-186, 195-203, 212-236
src/uiprotect/data/websocket.py     144     65    55%   68-70, 78-83, 101-134, 138-150, 157-161, 169-172, 175-179, 183, 196, 202-205, 209-215, 219-225, 229, 233-238, 242-245, 248-251
src/uiprotect/stream.py              96     59    39%   31-36, 40, 44-47, 51-57, 60-74, 82-86, 89-97, 100-112, 125-150, 154-161, 166
src/uiprotect/utils.py              395    233    41%   50, 99-100, 110-119, 132-141, 146-149, 153, 175, 179, 183, 198-201, 231, 235, 240, 249-250, 267-285, 290-296, 301-308, 313, 321, 330-331, 341-342, 352-353, 358-363, 367-385, 391-400, 404-410, 419-448, 459-479, 484-494, 499-510, 516-521, 526-552, 557-563, 567-610, 626-629, 634-635, 649-653, 658-662, 667, 672-674, 679-681, 686-688, 693
src/uiprotect/websocket.py          137     89    35%   77, 81-110, 114-117, 120-163, 167-172, 176-179, 183-196, 200-203, 211-215
---------------------------------------------------------------
TOTAL                              5691   2642    54%

4 files skipped due to complete coverage.

================================================================== short test summary info ==================================================================
FAILED tests/test_api.py::test_get_camera_video - pydantic.v1.error_wrappers.ValidationError: 7 validation errors for Bootstrap

Results (2.95s):
       1 error
(uiprotect-py3.12) 

There is so much going on here I am struggling to see the wood for the trees. Is this something you have seen before?

@bdraco
Copy link
Member

bdraco commented Oct 28, 2024

Sorry I don’t remember how that’s intended to work. Some discovery will be needed

@ep1cman
Copy link
Collaborator Author

ep1cman commented Oct 29, 2024

Just checking, is this an action on myself or you?

@bdraco
Copy link
Member

bdraco commented Oct 29, 2024

Just checking, is this an action on myself or you?

I'm likely not going to be able to do that any time soon so you'll probably get to it before I can

@ep1cman
Copy link
Collaborator Author

ep1cman commented Nov 18, 2024

I was wondering if you have had a chance to look into why the tests don't work with data from the latest unifi releases? I am unable to test any tests I write till this is resolved because this PR relies on checking that the version is new enough.

@bdraco
Copy link
Member

bdraco commented Nov 18, 2024

I haven't had a large enough time block free to look at it yet. End of the year is crazy for me. It usually calms down right after the holidays

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants