pyunifiprotect


Namepyunifiprotect JSON
Version 6.0.1 PyPI version JSON
download
home_pageNone
SummaryUnofficial UniFi Protect Python API and CLI
upload_time2024-05-18 19:17:37
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseBusiness Source License Copyright (c) 2024 Christopher Bailey Change Date: 6 months from release Change License: MIT The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use. Effective on the Change Date, or six months after the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate. If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work. All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor. You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work. Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work. This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License). TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. Additional Use Grants For all use cases, this software is considered licensed as the Change License and may be used under those terms. There are only two exceptions that are not granted usage: * Ubiquiti may not use the Licensed Work for any "professional" use, including, but not limited to development, tooling or documentation * The Licensed Work may not be forked and used for the purpose of making any kind of add-on, integration or component for Home Assistant. Third party or otherwise. The only version of the Licensed Work that may be used in Home Assistant (before the Change Date) is original one.
keywords home assistant python surveilance unifi unifi protect unifiprotect
VCS
bugtrack_url
requirements aiofiles aiohttp aioshutil aiosignal aiosqlite annotated-types asttokens asyncify attrs av click dateparser decorator executing frozenlist funkify greenlet idna ipython jedi markdown-it-py matplotlib-inline mdurl multidict orjson packaging parso pexpect pillow platformdirs prompt-toolkit ptyprocess pure-eval pydantic pydantic-core pygments pyjwt python-dateutil python-dotenv pytz regex rich shellingham six sqlalchemy stack-data termcolor traitlets typer typing-extensions tzlocal wcwidth xtyping yarl
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Unofficial UniFi Protect Python API and CLI

[![Latest PyPI version](https://img.shields.io/pypi/v/pyunifiprotect)](https://pypi.org/project/pyunifiprotect/) [![Supported Python](https://img.shields.io/pypi/pyversions/pyunifiprotect)](https://pypi.org/project/pyunifiprotect/) [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![CI](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/ci.yaml/badge.svg)](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/ci.yaml) [![Documentation](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/pages/pages-build-deployment/badge.svg)](https://angellusmortis.github.io/pyunifiprotect/)

`pyunifiprotect` is an unofficial API for UniFi Protect. There is no affiliation with Ubiquiti.

This module communicates with UniFi Protect surveillance software installed on a UniFi OS Console such as a Ubiquiti CloudKey+ or UniFi Dream Machine Pro.

The API is not documented by Ubiquiti, so there might be misses and/or frequent changes in this module, as Ubiquiti evolves the software.

The module is primarily written for the purpose of being used in Home Assistant core [integration for UniFi Protect](https://www.home-assistant.io/integrations/unifiprotect) but might be used for other purposes also.

## Smart Detections now Require Remote Access to enable

Smart Detections (person, vehicle, animal, face), a feature that previously could be used with local only console, [now requires you to enable remote access to enable](https://community.ui.com/questions/Cannot-enable-Smart-Detections/e3d50641-5c00-4607-9723-453cda557e35#answer/1d146426-89aa-4022-a0ae-fd5000846028).

Enabling Remote Access may grant other users access to your console [due to the fact Ubiquiti can reconfigure access controls at any time](https://community.ui.com/questions/Bug-Fix-Cloud-Access-Misconfiguration/fe8d4479-e187-4471-bf95-b2799183ceb7).

If you are not okay with the feature being locked behind Remote Access access, [let Ubiquiti know](https://community.ui.com/questions/Cannot-enable-Smart-Detections/e3d50641-5c00-4607-9723-453cda557e35).

## Documentation

[Full documentation for the project](https://angellusmortis.github.io/pyunifiprotect/).

## Requirements

If you want to install `pyunifiprotect` natively, the below are the requirements:

* [UniFi Protect](https://ui.com/camera-security) version 1.20+
    * Latest version of library is generally only tested against the two latest minor version. This is either two latest stable versions (such as 1.21.x and 2.0.x) or the latest EA version and stable version (such as 2.2.x EA and 2.1.x).
* [Python](https://www.python.org/) 3.9+
* POSIX compatible system
    * Library is only test on Linux, specifically the latest Debian version available for the official Python Docker images, but there is no reason the library should not work on any Linux distro or MacOS.
* [ffmpeg](https://ffmpeg.org/)
    * ffmpeg is primarily only for streaming audio to Protect cameras, this can be considered a soft requirement

Alternatively you can use the [provided Docker container](#using-docker-container), in which case the only requirement is [Docker](https://docs.docker.com/desktop/) or another OCI compatible orchestrator (such as Kubernetes or podman).

Windows is **not supported**. If you need to use `pyunifiprotect` on Windows, use Docker Desktop and the provided docker container or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).

## Install

### From PyPi

`pyunifiprotect` is available on PyPi:

```bash
pip install pyunifiprotect
```

### From Github

```bash
pip install git+https://github.com/AngellusMortis/pyunifiprotect.git#egg=pyunifiprotect
```

### Using Docker Container

A Docker container is also provided so you do not need to install/manage Python as well. You can add the following to your `.bashrc` or similar.

```bash
function unifi-protect() {
    docker run --rm -it \
      -e UFP_USERNAME=YOUR_USERNAME_HERE \
      -e UFP_PASSWORD=YOUR_PASSWORD_HERE \
      -e UFP_ADDRESS=YOUR_IP_ADDRESS \
      -e UFP_PORT=443 \
      -e UFP_SSL_VERIFY=True \
      -e TZ=America/New_York \
      -v $PWD:/data ghcr.io/angellusmortis/pyunifiprotect:latest "$@"
}
```

Some notes about the Docker version since it is running inside of a container:

* You can update at any time using the command `docker pull ghcr.io/AngellusMortis/pyunifiprotect:latest`
* Your local current working directory (`$PWD`) will automatically be mounted to `/data` inside of the container. For commands that output files, this is the _only_ path you can write to and have the file persist.
* The container supports `linux/amd64` and `linux/arm64` natively. This means it will also work well on MacOS or Windows using Docker Desktop.
* For versions of `pyunifiprotect` before v4.1.5, you need to use the `ghcr.io/briis/pyunifiprotect` image instead.
* `TZ` should be the [Olson timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for the timezone your UniFi Protect instance is in.
* For more details on `TZ` and other environment variables, check the [command line docs](https://angellusmortis.github.io/pyunifiprotect/latest/cli/)

## Quickstart

### CLI

!!! warning "About Ubiquiti SSO accounts"
    Ubiquiti SSO accounts are not supported and actively discouraged from being used. There is no option to use MFA. You are expected to use local access user. `pyunifiprotect` is not designed to allow you to use your owner account to access the your console or to be used over the public Internet as both pose a security risk.

```bash
export UFP_USERNAME=YOUR_USERNAME_HERE
export UFP_PASSWORD=YOUR_PASSWORD_HERE
export UFP_ADDRESS=YOUR_IP_ADDRESS
export UFP_PORT=443
# change to false if you do not have a valid HTTPS Certificate for your instance
export UFP_SSL_VERIFY=True

unifi-protect --help
unifi-protect nvr
```

### Python

UniFi Protect itself is 100% async, so as such this library is primarily designed to be used in an async context.

The main interface for the library is the `pyunifiprotect.ProtectApiClient`:

```python
from pyunifiprotect import ProtectApiClient

protect = ProtectApiClient(host, port, username, password, verify_ssl=True)

await protect.update() # this will initialize the protect .bootstrap and open a Websocket connection for updates

# get names of your cameras
for camera in protect.bootstrap.cameras.values():
    print(camera.name)

# subscribe to Websocket for updates to UFP
def callback(msg: WSSubscriptionMessage):
    # do stuff

unsub = protect.subscribe_websocket(callback)

# remove subscription
unsub()

```

## TODO / Planned / Not Implemented

Generally any feature missing from the library is planned to be done eventually / nice to have with the following exceptions

### UniFi OS Features

Anything that is strictly a UniFi OS feature. If it ever done, it will be in a separate library that interacts with this one. Examples include:

* Managing RAID and disks
* Creating and managing users

### Remote Access / Ubiquiti Cloud Features

Anything that requires a Ubiquiti Account or "Remote Access" to be enabled is never going to be implemented by me
([@AngellusMortis](https://github.com/AngellusMortis/)) as I support UniFi Protect as a 100% local only product. PRs are welcome to implement any related
features though.

Examples include:

* Stream sharing
* Smart Detections, including person, vehicle, animals license plate and faces

## Credits

* Bjarne Riis ([@briis](https://github.com/briis/)) for the original pyunifiprotect package

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pyunifiprotect",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "Christopher Bailey <cbailey@mort.is>, \"J. Nick Koston\" <nick@koston.org>",
    "keywords": "Home Assistant, Python, Surveilance, UniFi, UniFi Protect, UniFiProtect",
    "author": null,
    "author_email": "Bjarne Riis <bjarne@briis.com>, Christopher Bailey <cbailey@mort.is>",
    "download_url": "https://files.pythonhosted.org/packages/7a/dc/847f224a3f6ccadd2fe68b4b1fe1f8a30f96dcc6a08f61a5a3d929261afb/pyunifiprotect-6.0.1.tar.gz",
    "platform": null,
    "description": "# Unofficial UniFi Protect Python API and CLI\n\n[![Latest PyPI version](https://img.shields.io/pypi/v/pyunifiprotect)](https://pypi.org/project/pyunifiprotect/) [![Supported Python](https://img.shields.io/pypi/pyversions/pyunifiprotect)](https://pypi.org/project/pyunifiprotect/) [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![CI](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/ci.yaml/badge.svg)](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/ci.yaml) [![Documentation](https://github.com/AngellusMortis/pyunifiprotect/actions/workflows/pages/pages-build-deployment/badge.svg)](https://angellusmortis.github.io/pyunifiprotect/)\n\n`pyunifiprotect` is an unofficial API for UniFi Protect. There is no affiliation with Ubiquiti.\n\nThis module communicates with UniFi Protect surveillance software installed on a UniFi OS Console such as a Ubiquiti CloudKey+ or UniFi Dream Machine Pro.\n\nThe API is not documented by Ubiquiti, so there might be misses and/or frequent changes in this module, as Ubiquiti evolves the software.\n\nThe module is primarily written for the purpose of being used in Home Assistant core [integration for UniFi Protect](https://www.home-assistant.io/integrations/unifiprotect) but might be used for other purposes also.\n\n## Smart Detections now Require Remote Access to enable\n\nSmart Detections (person, vehicle, animal, face), a feature that previously could be used with local only console, [now requires you to enable remote access to enable](https://community.ui.com/questions/Cannot-enable-Smart-Detections/e3d50641-5c00-4607-9723-453cda557e35#answer/1d146426-89aa-4022-a0ae-fd5000846028).\n\nEnabling Remote Access may grant other users access to your console [due to the fact Ubiquiti can reconfigure access controls at any time](https://community.ui.com/questions/Bug-Fix-Cloud-Access-Misconfiguration/fe8d4479-e187-4471-bf95-b2799183ceb7).\n\nIf you are not okay with the feature being locked behind Remote Access access, [let Ubiquiti know](https://community.ui.com/questions/Cannot-enable-Smart-Detections/e3d50641-5c00-4607-9723-453cda557e35).\n\n## Documentation\n\n[Full documentation for the project](https://angellusmortis.github.io/pyunifiprotect/).\n\n## Requirements\n\nIf you want to install `pyunifiprotect` natively, the below are the requirements:\n\n* [UniFi Protect](https://ui.com/camera-security) version 1.20+\n    * Latest version of library is generally only tested against the two latest minor version. This is either two latest stable versions (such as 1.21.x and 2.0.x) or the latest EA version and stable version (such as 2.2.x EA and 2.1.x).\n* [Python](https://www.python.org/) 3.9+\n* POSIX compatible system\n    * Library is only test on Linux, specifically the latest Debian version available for the official Python Docker images, but there is no reason the library should not work on any Linux distro or MacOS.\n* [ffmpeg](https://ffmpeg.org/)\n    * ffmpeg is primarily only for streaming audio to Protect cameras, this can be considered a soft requirement\n\nAlternatively you can use the [provided Docker container](#using-docker-container), in which case the only requirement is [Docker](https://docs.docker.com/desktop/) or another OCI compatible orchestrator (such as Kubernetes or podman).\n\nWindows is **not supported**. If you need to use `pyunifiprotect` on Windows, use Docker Desktop and the provided docker container or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).\n\n## Install\n\n### From PyPi\n\n`pyunifiprotect` is available on PyPi:\n\n```bash\npip install pyunifiprotect\n```\n\n### From Github\n\n```bash\npip install git+https://github.com/AngellusMortis/pyunifiprotect.git#egg=pyunifiprotect\n```\n\n### Using Docker Container\n\nA Docker container is also provided so you do not need to install/manage Python as well. You can add the following to your `.bashrc` or similar.\n\n```bash\nfunction unifi-protect() {\n    docker run --rm -it \\\n      -e UFP_USERNAME=YOUR_USERNAME_HERE \\\n      -e UFP_PASSWORD=YOUR_PASSWORD_HERE \\\n      -e UFP_ADDRESS=YOUR_IP_ADDRESS \\\n      -e UFP_PORT=443 \\\n      -e UFP_SSL_VERIFY=True \\\n      -e TZ=America/New_York \\\n      -v $PWD:/data ghcr.io/angellusmortis/pyunifiprotect:latest \"$@\"\n}\n```\n\nSome notes about the Docker version since it is running inside of a container:\n\n* You can update at any time using the command `docker pull ghcr.io/AngellusMortis/pyunifiprotect:latest`\n* Your local current working directory (`$PWD`) will automatically be mounted to `/data` inside of the container. For commands that output files, this is the _only_ path you can write to and have the file persist.\n* The container supports `linux/amd64` and `linux/arm64` natively. This means it will also work well on MacOS or Windows using Docker Desktop.\n* For versions of `pyunifiprotect` before v4.1.5, you need to use the `ghcr.io/briis/pyunifiprotect` image instead.\n* `TZ` should be the [Olson timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for the timezone your UniFi Protect instance is in.\n* For more details on `TZ` and other environment variables, check the [command line docs](https://angellusmortis.github.io/pyunifiprotect/latest/cli/)\n\n## Quickstart\n\n### CLI\n\n!!! warning \"About Ubiquiti SSO accounts\"\n    Ubiquiti SSO accounts are not supported and actively discouraged from being used. There is no option to use MFA. You are expected to use local access user. `pyunifiprotect` is not designed to allow you to use your owner account to access the your console or to be used over the public Internet as both pose a security risk.\n\n```bash\nexport UFP_USERNAME=YOUR_USERNAME_HERE\nexport UFP_PASSWORD=YOUR_PASSWORD_HERE\nexport UFP_ADDRESS=YOUR_IP_ADDRESS\nexport UFP_PORT=443\n# change to false if you do not have a valid HTTPS Certificate for your instance\nexport UFP_SSL_VERIFY=True\n\nunifi-protect --help\nunifi-protect nvr\n```\n\n### Python\n\nUniFi Protect itself is 100% async, so as such this library is primarily designed to be used in an async context.\n\nThe main interface for the library is the `pyunifiprotect.ProtectApiClient`:\n\n```python\nfrom pyunifiprotect import ProtectApiClient\n\nprotect = ProtectApiClient(host, port, username, password, verify_ssl=True)\n\nawait protect.update() # this will initialize the protect .bootstrap and open a Websocket connection for updates\n\n# get names of your cameras\nfor camera in protect.bootstrap.cameras.values():\n    print(camera.name)\n\n# subscribe to Websocket for updates to UFP\ndef callback(msg: WSSubscriptionMessage):\n    # do stuff\n\nunsub = protect.subscribe_websocket(callback)\n\n# remove subscription\nunsub()\n\n```\n\n## TODO / Planned / Not Implemented\n\nGenerally any feature missing from the library is planned to be done eventually / nice to have with the following exceptions\n\n### UniFi OS Features\n\nAnything that is strictly a UniFi OS feature. If it ever done, it will be in a separate library that interacts with this one. Examples include:\n\n* Managing RAID and disks\n* Creating and managing users\n\n### Remote Access / Ubiquiti Cloud Features\n\nAnything that requires a Ubiquiti Account or \"Remote Access\" to be enabled is never going to be implemented by me\n([@AngellusMortis](https://github.com/AngellusMortis/)) as I support UniFi Protect as a 100% local only product. PRs are welcome to implement any related\nfeatures though.\n\nExamples include:\n\n* Stream sharing\n* Smart Detections, including person, vehicle, animals license plate and faces\n\n## Credits\n\n* Bjarne Riis ([@briis](https://github.com/briis/)) for the original pyunifiprotect package\n",
    "bugtrack_url": null,
    "license": "Business Source License  Copyright (c) 2024 Christopher Bailey  Change Date: 6 months from release Change License: MIT  The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.  Effective on the Change Date, or six months after the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.  If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.  All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.  You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.  Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.  This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).  TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN \"AS IS\" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.   Additional Use Grants  For all use cases, this software is considered licensed as the Change License and may be used under those terms. There are only two exceptions that are not granted usage:  * Ubiquiti may not use the Licensed Work for any \"professional\" use, including, but not limited to development, tooling or documentation * The Licensed Work may not be forked and used for the purpose of making any kind of add-on, integration or component for Home Assistant. Third party or otherwise. The only version of the Licensed Work that may be used in Home Assistant (before the Change Date) is original one.",
    "summary": "Unofficial UniFi Protect Python API and CLI",
    "version": "6.0.1",
    "project_urls": {
        "Bug Reports": "https://github.com/AngellusMortis/pyunifiprotect/issues/",
        "Changelog": "https://github.com/AngellusMortis/pyunifiprotect/releases/",
        "Documentation": "https://angellusmortis.github.io/pyunifiprotect/latest/",
        "Source Code": "https://github.com/AngellusMortis/pyunifiprotect/"
    },
    "split_keywords": [
        "home assistant",
        " python",
        " surveilance",
        " unifi",
        " unifi protect",
        " unifiprotect"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7b3ca1aab2b4b142de97da58a72aad1bc383dc52dcd9f045a402802dafb0effe",
                "md5": "e2378e7aef45adf82154335210cad604",
                "sha256": "3462d1006965f731054b1b155ea979e57ed372f9e3ec8fd67e9f7d8458e763c3"
            },
            "downloads": -1,
            "filename": "pyunifiprotect-6.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e2378e7aef45adf82154335210cad604",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 126047,
            "upload_time": "2024-05-18T19:17:33",
            "upload_time_iso_8601": "2024-05-18T19:17:33.897829Z",
            "url": "https://files.pythonhosted.org/packages/7b/3c/a1aab2b4b142de97da58a72aad1bc383dc52dcd9f045a402802dafb0effe/pyunifiprotect-6.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7adc847f224a3f6ccadd2fe68b4b1fe1f8a30f96dcc6a08f61a5a3d929261afb",
                "md5": "3e8d4cf80d6199da97eff28e0e13a46b",
                "sha256": "d74f6dcf585a2e0daa90ee62c9bf8ec23710b239dafc2bcc934106455d823161"
            },
            "downloads": -1,
            "filename": "pyunifiprotect-6.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "3e8d4cf80d6199da97eff28e0e13a46b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 107328,
            "upload_time": "2024-05-18T19:17:37",
            "upload_time_iso_8601": "2024-05-18T19:17:37.125068Z",
            "url": "https://files.pythonhosted.org/packages/7a/dc/847f224a3f6ccadd2fe68b4b1fe1f8a30f96dcc6a08f61a5a3d929261afb/pyunifiprotect-6.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-18 19:17:37",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "AngellusMortis",
    "github_project": "pyunifiprotect",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "aiofiles",
            "specs": [
                [
                    "==",
                    "23.2.1"
                ]
            ]
        },
        {
            "name": "aiohttp",
            "specs": [
                [
                    "==",
                    "3.9.5"
                ]
            ]
        },
        {
            "name": "aioshutil",
            "specs": [
                [
                    "==",
                    "1.3"
                ]
            ]
        },
        {
            "name": "aiosignal",
            "specs": [
                [
                    "==",
                    "1.3.1"
                ]
            ]
        },
        {
            "name": "aiosqlite",
            "specs": [
                [
                    "==",
                    "0.20.0"
                ]
            ]
        },
        {
            "name": "annotated-types",
            "specs": [
                [
                    "==",
                    "0.6.0"
                ]
            ]
        },
        {
            "name": "asttokens",
            "specs": [
                [
                    "==",
                    "2.4.1"
                ]
            ]
        },
        {
            "name": "asyncify",
            "specs": [
                [
                    "==",
                    "0.10.0"
                ]
            ]
        },
        {
            "name": "attrs",
            "specs": [
                [
                    "==",
                    "23.2.0"
                ]
            ]
        },
        {
            "name": "av",
            "specs": [
                [
                    "==",
                    "12.0.0"
                ]
            ]
        },
        {
            "name": "click",
            "specs": [
                [
                    "==",
                    "8.1.7"
                ]
            ]
        },
        {
            "name": "dateparser",
            "specs": [
                [
                    "==",
                    "1.2.0"
                ]
            ]
        },
        {
            "name": "decorator",
            "specs": [
                [
                    "==",
                    "5.1.1"
                ]
            ]
        },
        {
            "name": "executing",
            "specs": [
                [
                    "==",
                    "2.0.1"
                ]
            ]
        },
        {
            "name": "frozenlist",
            "specs": [
                [
                    "==",
                    "1.4.1"
                ]
            ]
        },
        {
            "name": "funkify",
            "specs": [
                [
                    "==",
                    "0.4.5"
                ]
            ]
        },
        {
            "name": "greenlet",
            "specs": [
                [
                    "==",
                    "3.0.3"
                ]
            ]
        },
        {
            "name": "idna",
            "specs": [
                [
                    "==",
                    "3.7"
                ]
            ]
        },
        {
            "name": "ipython",
            "specs": [
                [
                    "==",
                    "8.18.1"
                ]
            ]
        },
        {
            "name": "jedi",
            "specs": [
                [
                    "==",
                    "0.19.1"
                ]
            ]
        },
        {
            "name": "markdown-it-py",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "matplotlib-inline",
            "specs": [
                [
                    "==",
                    "0.1.7"
                ]
            ]
        },
        {
            "name": "mdurl",
            "specs": [
                [
                    "==",
                    "0.1.2"
                ]
            ]
        },
        {
            "name": "multidict",
            "specs": [
                [
                    "==",
                    "6.0.5"
                ]
            ]
        },
        {
            "name": "orjson",
            "specs": [
                [
                    "==",
                    "3.10.3"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "24.0"
                ]
            ]
        },
        {
            "name": "parso",
            "specs": [
                [
                    "==",
                    "0.8.4"
                ]
            ]
        },
        {
            "name": "pexpect",
            "specs": [
                [
                    "==",
                    "4.9.0"
                ]
            ]
        },
        {
            "name": "pillow",
            "specs": [
                [
                    "==",
                    "10.3.0"
                ]
            ]
        },
        {
            "name": "platformdirs",
            "specs": [
                [
                    "==",
                    "4.2.2"
                ]
            ]
        },
        {
            "name": "prompt-toolkit",
            "specs": [
                [
                    "==",
                    "3.0.43"
                ]
            ]
        },
        {
            "name": "ptyprocess",
            "specs": [
                [
                    "==",
                    "0.7.0"
                ]
            ]
        },
        {
            "name": "pure-eval",
            "specs": [
                [
                    "==",
                    "0.2.2"
                ]
            ]
        },
        {
            "name": "pydantic",
            "specs": [
                [
                    "==",
                    "2.7.1"
                ]
            ]
        },
        {
            "name": "pydantic-core",
            "specs": [
                [
                    "==",
                    "2.18.2"
                ]
            ]
        },
        {
            "name": "pygments",
            "specs": [
                [
                    "==",
                    "2.18.0"
                ]
            ]
        },
        {
            "name": "pyjwt",
            "specs": [
                [
                    "==",
                    "2.8.0"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.9.0.post0"
                ]
            ]
        },
        {
            "name": "python-dotenv",
            "specs": [
                [
                    "==",
                    "1.0.1"
                ]
            ]
        },
        {
            "name": "pytz",
            "specs": [
                [
                    "==",
                    "2024.1"
                ]
            ]
        },
        {
            "name": "regex",
            "specs": [
                [
                    "==",
                    "2024.5.15"
                ]
            ]
        },
        {
            "name": "rich",
            "specs": [
                [
                    "==",
                    "13.7.1"
                ]
            ]
        },
        {
            "name": "shellingham",
            "specs": [
                [
                    "==",
                    "1.5.4"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.16.0"
                ]
            ]
        },
        {
            "name": "sqlalchemy",
            "specs": [
                [
                    "==",
                    "2.0.30"
                ]
            ]
        },
        {
            "name": "stack-data",
            "specs": [
                [
                    "==",
                    "0.6.3"
                ]
            ]
        },
        {
            "name": "termcolor",
            "specs": [
                [
                    "==",
                    "2.4.0"
                ]
            ]
        },
        {
            "name": "traitlets",
            "specs": [
                [
                    "==",
                    "5.14.3"
                ]
            ]
        },
        {
            "name": "typer",
            "specs": [
                [
                    "==",
                    "0.12.3"
                ]
            ]
        },
        {
            "name": "typing-extensions",
            "specs": [
                [
                    "==",
                    "4.11.0"
                ]
            ]
        },
        {
            "name": "tzlocal",
            "specs": [
                [
                    "==",
                    "5.2"
                ]
            ]
        },
        {
            "name": "wcwidth",
            "specs": [
                [
                    "==",
                    "0.2.13"
                ]
            ]
        },
        {
            "name": "xtyping",
            "specs": [
                [
                    "==",
                    "0.8.2"
                ]
            ]
        },
        {
            "name": "yarl",
            "specs": [
                [
                    "==",
                    "1.9.4"
                ]
            ]
        }
    ],
    "lcname": "pyunifiprotect"
}
        
Elapsed time: 2.67626s