Initial Commit
Really should have added this to version control earlier, but was trying to fix all the pre-commit issues first...
This commit is contained in:
commit
5b0c72dbef
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
25
.pre-commit-config.yaml
Normal file
25
.pre-commit-config.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.5.2
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
5
LICENSE
Normal file
5
LICENSE
Normal file
@ -0,0 +1,5 @@
|
||||
Copyright 2024 Adam Goldsmith
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Python UniFi Access API Client
|
||||
|
||||
Unofficial typed wrapper for the public UniFi Access API.
|
||||
Currently written against V2.2.10 of the [API Reference](https://core-config-gfoz.uid.alpha.ui.com/configs/unifi-access/api_reference.pdf).
|
||||
|
||||
## Completion Status
|
||||
See [docs/completion.md](docs/completion.md)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This library is not affiliated with or endorsed by Ubiquiti Networks Inc.
|
||||
It probably contains at least some errors or mismatch with the actual API behaviour.
|
||||
Feel free to open issues or submit pull requests.
|
3
docs/api_reference/client.md
Normal file
3
docs/api_reference/client.md
Normal file
@ -0,0 +1,3 @@
|
||||
::: unifi_access.AccessClient
|
||||
options:
|
||||
show_root_heading: true
|
3
docs/api_reference/schemas/03_user.md
Normal file
3
docs/api_reference/schemas/03_user.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 3. User
|
||||
|
||||
::: unifi_access.schemas._user
|
3
docs/api_reference/schemas/04_visitor.md
Normal file
3
docs/api_reference/schemas/04_visitor.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 4. Visitor
|
||||
|
||||
::: unifi_access.schemas._visitor
|
2
docs/api_reference/schemas/05_access_policy.md
Normal file
2
docs/api_reference/schemas/05_access_policy.md
Normal file
@ -0,0 +1,2 @@
|
||||
# 5. Access Policy
|
||||
::: unifi_access.schemas._access_policy
|
3
docs/api_reference/schemas/06_credential.md
Normal file
3
docs/api_reference/schemas/06_credential.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 6. Credential
|
||||
|
||||
::: unifi_access.schemas._credential
|
3
docs/api_reference/schemas/07_space.md
Normal file
3
docs/api_reference/schemas/07_space.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 7. Space
|
||||
|
||||
::: unifi_access.schemas._space
|
3
docs/api_reference/schemas/08_device.md
Normal file
3
docs/api_reference/schemas/08_device.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 8. Device
|
||||
|
||||
::: unifi_access.schemas._device
|
3
docs/api_reference/schemas/09_system_log.md
Normal file
3
docs/api_reference/schemas/09_system_log.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 9. System Log
|
||||
|
||||
::: unifi_access.schemas._system_log
|
3
docs/api_reference/schemas/10_identity.md
Normal file
3
docs/api_reference/schemas/10_identity.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 10. UniFi Identity
|
||||
|
||||
::: unifi_access.schemas._identity
|
2
docs/api_reference/schemas/index.md
Normal file
2
docs/api_reference/schemas/index.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Schemas
|
||||
::: unifi_access.schemas._base
|
102
docs/completion.md
Normal file
102
docs/completion.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Completion
|
||||
|
||||
* AccessClient -> implemented in the client
|
||||
* offline/examples test -> has offline tests based on examples in the API reference pdf
|
||||
* live test -> is used in a test that runs against a real instance
|
||||
|
||||
| Section/Endpoint | AccessClient | offline/examples test | live test |
|
||||
|------------------------------------------------------------|--------------------|-----------------------|--------------------|
|
||||
| 3. User | :white_check_mark: | :white_check_mark: | |
|
||||
| └─ 3.2 User Registration | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.3 Update User | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.4 Fetch User | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.5 Fetch All Users | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.6 Assign Access Policy to User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.7 Assign NFC Card to User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.8 Unassign NFC Card from User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.9 Assign PIN Code to User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.10 Unassign PIN Code from User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.11 Create User Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.12 Fetch All User Groups | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.13 Fetch User Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.14 Update User Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.15 Delete User Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.16 Assign User to User Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.17 Unassign User from User Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.18 Fetch Users in a User Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 3.19 Fetch All Users in a User Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.20 Fetch the Access Policies Assigned to a User | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.21 Assign Access Policy to User Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 3.22 Fetch the Access Policies Assigned to a User Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| 4. Visitor | :white_check_mark: | :white_check_mark: | |
|
||||
| └─ 4.2 Create Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.3 Fetch Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.4 Fetch All Visitors | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.5 Update Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.6 Delete Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.7 Assign NFC Card To Visitor | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 4.8 Unassign NFC Card From Visitor | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 4.9 Assign PIN Code To Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 4.10 Unassign PIN Code From Visitor | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| 5. Access Policy | :white_check_mark: | :white_check_mark: | |
|
||||
| └─ 5.2 Create Access Policy | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.3 Update Access Policy | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.4 Delete Access Policy | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.5 Fetch Access Policy | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.6 Fetch All Access Policies | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.8 Create Holiday Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.9 Update Holiday Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.10 Delete Holiday Group | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.11 Fetch Holiday Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.12 Fetch All Holiday Groups | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.14 Create Schedule | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| └─ 5.15 Update Schedule | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.16 Fetch Schedule | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.17 Fetch All Schedules | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 5.18 Delete Schedule | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| 6. Credential | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.1 Generate PIN Code | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.2 Enroll NFC Card | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.3 Fetch NFC Card Enrollment Status | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.4 Remove a Session Created for NFC Card Enrollment | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.7 Fetch NFC Card | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.8 Fetch All NFC Cards | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 6.9 Delete NFC Card | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| 7. Space | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.1 Fetch Door Group Topology | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.2 Create Door Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.3 Fetch Door Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.4 Update Door Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.5 Fetch All Door Groups | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.6 Delete Door Group | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.7 Fetch Door | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.8 Fetch All Doors | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.9 Remote Door Unlocking | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.10 Set Temporary Door Locking Rule | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.11 Fetch Door Locking Rule | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.12 Set Door Emergency Status | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 7.13 Fetch Door Emergency Status | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| 8. Device | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 8.1 Fetch Devices | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| 9. System Log | :white_check_mark: | | :x: |
|
||||
| └─ 9.2 Fetch System Logs | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 9.3 Export System Logs | :x: | :white_check_mark: | :x: |
|
||||
| └─ 9.4 Fetch Resources in System Logs | :x: | :x: | :x: |
|
||||
| └─ 9.5 Fetch Static Resources in System Logs | :x: | :x: | :x: |
|
||||
| 10. UniFi Identity | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.1 Send UniFi Identity Invitations | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.2 Fetch Available Resources | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.3 Assign Resources to Users | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.4 Fetch Resources Assigned to Users | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.5 Assign Resources to User Groups | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| └─ 10.6 Fetch the Resources Assigned to User Groups | :white_check_mark: | :white_check_mark: | :x: |
|
||||
| 11. Notification | :x: | :x: | :x: |
|
||||
| └─ 11.1 Fetch Notifications [WebSocket] | :x: | :x: | :x: |
|
||||
| └─ 11.2 List of Supported Webhook Events [Webhook] | :x: | :x: | :x: |
|
||||
| └─ 11.3 Fetch Webhook Endpoints List [Webhook] | :x: | :x: | :x: |
|
||||
| └─ 11.4 Add Webhook Endpoints [Webhook] | :x: | :x: | :x: |
|
||||
| └─ 11.5 Update Webhook Endpoints [Webhook] | :x: | :x: | :x: |
|
||||
| └─ 11.6 Delete Webhook Endpoints [Webhook] | :x: | :x: | :x: |
|
||||
| 12. API Server | :x: | :x: | :x: |
|
||||
| └─ 12.1 Upload HTTPS Certificate | :x: | :x: | :x: |
|
||||
| └─ 12.2 Delete HTTPS Certificate | :x: | :x: | :x: |
|
1
docs/index.md
Normal file
1
docs/index.md
Normal file
@ -0,0 +1 @@
|
||||
--8<-- "README.md"
|
8
docs/stylesheets/extra.css
Normal file
8
docs/stylesheets/extra.css
Normal file
@ -0,0 +1,8 @@
|
||||
.md-nav--secondary > .md-nav__list > .md-nav__item > .md-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md-nav--secondary > .md-nav__list > .md-nav__item:has(.md-nav__link--active) > .md-nav
|
||||
{
|
||||
display: initial;
|
||||
}
|
58
mkdocs.yml
Normal file
58
mkdocs.yml
Normal file
@ -0,0 +1,58 @@
|
||||
site_name: Python UniFi Access Client
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- mkdocstrings:
|
||||
handlers:
|
||||
python:
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
group_by_category: true
|
||||
members_order: source
|
||||
show_symbol_type_heading: true
|
||||
show_root_toc_entry: false
|
||||
show_symbol_type_toc: true
|
||||
docstring_style: sphinx
|
||||
merge_init_into_class: true
|
||||
show_signature_annotations: true
|
||||
separate_signature: true
|
||||
signature_crossrefs: true
|
||||
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- toc.follow
|
||||
- navigation.tracking
|
||||
- navigation.indexes
|
||||
- navigation.expand
|
||||
- navigation.sections
|
||||
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Switch to light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to system preference
|
||||
|
||||
watch:
|
||||
- src
|
||||
|
||||
markdown_extensions:
|
||||
- tables
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- pymdownx.snippets
|
||||
- pymdownx.emoji
|
||||
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
95
pyproject.toml
Normal file
95
pyproject.toml
Normal file
@ -0,0 +1,95 @@
|
||||
[build-system]
|
||||
build-backend = "hatchling.build"
|
||||
requires = [ "hatchling" ]
|
||||
|
||||
[project]
|
||||
name = "unifi-access"
|
||||
version = "0.1.0"
|
||||
description = "Typed wrapper for the Unifi Access Public API"
|
||||
readme = "README.md"
|
||||
license = { text = "ISC" }
|
||||
authors = [
|
||||
{ name = "Adam Goldsmith", email = "contact@adamgoldsmith.name" },
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: ISC License (ISCL)",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"pydantic>=2.9.2",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"mypy>=1.13",
|
||||
"pytest>=8.3.3",
|
||||
"responses>=0.25.3",
|
||||
"ruff>=0.7.1",
|
||||
"types-requests>=2.32.0.20241016",
|
||||
]
|
||||
docs = [
|
||||
"mkdocs-material>=9.5.42",
|
||||
"mkdocstrings[python]>=0.26.2",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
lint.select = [
|
||||
"A",
|
||||
"B",
|
||||
"C4",
|
||||
"E4",
|
||||
"E7",
|
||||
"E9",
|
||||
"F",
|
||||
"FIX003",
|
||||
"FURB",
|
||||
"I",
|
||||
"INP",
|
||||
"ISC",
|
||||
"LOG",
|
||||
"PERF",
|
||||
"PIE",
|
||||
"PL",
|
||||
"PT",
|
||||
"PTH",
|
||||
"Q",
|
||||
"RSE",
|
||||
"SIM",
|
||||
"T20",
|
||||
"TCH",
|
||||
"UP",
|
||||
]
|
||||
lint.ignore = [ "ISC001" ]
|
||||
|
||||
[tool.pyproject-fmt]
|
||||
indent = 4
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
filterwarnings = [ "ignore::urllib3.exceptions.InsecureRequestWarning" ]
|
||||
|
||||
[tool.mypy]
|
||||
plugins = [ "pydantic.mypy" ]
|
||||
|
||||
follow_imports = "silent"
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
disallow_any_generics = true
|
||||
check_untyped_defs = true
|
||||
no_implicit_reexport = true
|
||||
disallow_untyped_defs = true
|
||||
|
||||
[tool.pydantic-mypy]
|
||||
init_forbid_extra = true
|
||||
init_typed = true
|
||||
warn_required_dynamic_aliases = true
|
||||
|
||||
[tool.uv]
|
||||
default-groups = [ "dev", "docs" ]
|
9
src/unifi_access/__init__.py
Normal file
9
src/unifi_access/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
Wrapper for Unifi Access API.
|
||||
|
||||
See the [official API Reference](https://core-config-gfoz.uid.alpha.ui.com/configs/unifi-access/api_reference.pdf) for more details.
|
||||
"""
|
||||
|
||||
from ._client import AccessClient, Response, ResponseCode
|
||||
|
||||
__all__ = ["AccessClient", "Response", "ResponseCode"]
|
1293
src/unifi_access/_client.py
Normal file
1293
src/unifi_access/_client.py
Normal file
File diff suppressed because it is too large
Load Diff
0
src/unifi_access/py.typed
Normal file
0
src/unifi_access/py.typed
Normal file
145
src/unifi_access/schemas/__init__.py
Normal file
145
src/unifi_access/schemas/__init__.py
Normal file
@ -0,0 +1,145 @@
|
||||
from ._access_policy import (
|
||||
AccessPolicy,
|
||||
FetchAllHolidayGroupsResponse,
|
||||
FetchAllSchedulesResponse,
|
||||
Holiday,
|
||||
HolidayGroup,
|
||||
PartialHoliday,
|
||||
PartialSchedule,
|
||||
Schedule,
|
||||
TimePeriod,
|
||||
WeekSchedule,
|
||||
)
|
||||
from ._base import (
|
||||
AccessPolicyId,
|
||||
ActorId,
|
||||
DeviceId,
|
||||
DoorGroupId,
|
||||
DoorGroupResource,
|
||||
DoorId,
|
||||
DoorResource,
|
||||
FloorId,
|
||||
HolidayGroupId,
|
||||
HolidayId,
|
||||
NamedDoorGroupResource,
|
||||
NamedDoorGroupResourceWithIsBindHub,
|
||||
NamedDoorResource,
|
||||
NamedDoorResourceWithIsBindHub,
|
||||
NamedResource,
|
||||
NamedResourceWithIsBindHub,
|
||||
NfcCardEnrollmentSessionId,
|
||||
NfcCardId,
|
||||
NfcCardToken,
|
||||
PinCode,
|
||||
Resource,
|
||||
ResourceId,
|
||||
ScheduleId,
|
||||
UserGroupId,
|
||||
UserId,
|
||||
VisitorId,
|
||||
)
|
||||
from ._credential import (
|
||||
EnrollNfcCardResponse,
|
||||
NfcCard,
|
||||
NfcCardEnrollmentStatus,
|
||||
NfcCardUser,
|
||||
)
|
||||
from ._device import Device
|
||||
from ._identity import (
|
||||
IdentityInvitationEmailFailure,
|
||||
IdentityInvitationUser,
|
||||
IdentityResourceType,
|
||||
)
|
||||
from ._space import (
|
||||
Door,
|
||||
DoorEmergencyStatus,
|
||||
DoorGroup,
|
||||
DoorGroupTopology,
|
||||
DoorGroupType,
|
||||
DoorLockingRule,
|
||||
DoorLockingRuleType,
|
||||
ResourceTopology,
|
||||
)
|
||||
from ._system_log import (
|
||||
SystemLogActor,
|
||||
SystemLogAuthentication,
|
||||
SystemLogEntry,
|
||||
SystemLogEvent,
|
||||
SystemLogTopic,
|
||||
)
|
||||
from ._user import FullUser, User, UserGroup, UserNfcCard, UserStatus
|
||||
from ._visitor import (
|
||||
FetchAllVisitorsExpansion,
|
||||
Visitor,
|
||||
VisitorStatus,
|
||||
VisitReason,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AccessPolicy",
|
||||
"FetchAllHolidayGroupsResponse",
|
||||
"FetchAllSchedulesResponse",
|
||||
"Holiday",
|
||||
"HolidayGroup",
|
||||
"PartialHoliday",
|
||||
"Schedule",
|
||||
"PartialSchedule",
|
||||
"TimePeriod",
|
||||
"WeekSchedule",
|
||||
"AccessPolicyId",
|
||||
"DoorId",
|
||||
"FloorId",
|
||||
"DoorGroupId",
|
||||
"ResourceId",
|
||||
"ScheduleId",
|
||||
"UserId",
|
||||
"UserGroupId",
|
||||
"VisitorId",
|
||||
"NfcCardId",
|
||||
"HolidayId",
|
||||
"HolidayGroupId",
|
||||
"DeviceId",
|
||||
"NfcCardEnrollmentSessionId",
|
||||
"NfcCardToken",
|
||||
"ActorId",
|
||||
"Resource",
|
||||
"NamedResource",
|
||||
"NamedResourceWithIsBindHub",
|
||||
"DoorGroupResource",
|
||||
"DoorResource",
|
||||
"NamedDoorGroupResource",
|
||||
"NamedDoorGroupResourceWithIsBindHub",
|
||||
"NamedDoorResource",
|
||||
"NamedDoorResourceWithIsBindHub",
|
||||
"PinCode",
|
||||
"EnrollNfcCardResponse",
|
||||
"NfcCard",
|
||||
"NfcCardEnrollmentStatus",
|
||||
"NfcCardUser",
|
||||
"Device",
|
||||
"IdentityInvitationEmailFailure",
|
||||
"IdentityInvitationUser",
|
||||
"IdentityResourceType",
|
||||
"Door",
|
||||
"DoorEmergencyStatus",
|
||||
"DoorGroup",
|
||||
"DoorGroupTopology",
|
||||
"DoorGroupType",
|
||||
"DoorLockingRule",
|
||||
"DoorLockingRuleType",
|
||||
"ResourceTopology",
|
||||
"SystemLogActor",
|
||||
"SystemLogAuthentication",
|
||||
"SystemLogEntry",
|
||||
"SystemLogEvent",
|
||||
"SystemLogTopic",
|
||||
"FetchAllVisitorsExpansion",
|
||||
"User",
|
||||
"FullUser",
|
||||
"UserGroup",
|
||||
"UserNfcCard",
|
||||
"UserStatus",
|
||||
"Visitor",
|
||||
"VisitorStatus",
|
||||
"VisitReason",
|
||||
]
|
151
src/unifi_access/schemas/_access_policy.py
Normal file
151
src/unifi_access/schemas/_access_policy.py
Normal file
@ -0,0 +1,151 @@
|
||||
from ._base import (
|
||||
AccessPolicyId,
|
||||
ForbidExtraBaseModel,
|
||||
FormattedTime,
|
||||
HolidayGroupId,
|
||||
HolidayId,
|
||||
Resource,
|
||||
RFC3339Datetime,
|
||||
ScheduleId,
|
||||
)
|
||||
|
||||
|
||||
class AccessPolicy(ForbidExtraBaseModel):
|
||||
"""5.1 Access Policy Schemas"""
|
||||
|
||||
id: AccessPolicyId
|
||||
"""Identity ID of the access policy."""
|
||||
name: str
|
||||
"""Name of the access policy."""
|
||||
resources: list[Resource]
|
||||
"""Specify the locations that can be accessed."""
|
||||
schedule_id: ScheduleId
|
||||
"""Identity ID of the schedule."""
|
||||
|
||||
|
||||
class Holiday(ForbidExtraBaseModel):
|
||||
description: str | None = None
|
||||
"""Description of the holiday."""
|
||||
id: HolidayId
|
||||
"""Identity ID of the holiday."""
|
||||
name: str
|
||||
"""Name of the holiday."""
|
||||
repeat: bool
|
||||
"""Indicate whether the holiday repeats annually."""
|
||||
start_time: RFC3339Datetime
|
||||
"""Start time of the holiday"""
|
||||
end_time: RFC3339Datetime
|
||||
"""End time of the holiday"""
|
||||
is_template: bool | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class PartialHoliday(ForbidExtraBaseModel):
|
||||
description: str | None = None
|
||||
"""Description of the holiday."""
|
||||
id: HolidayId | None = None
|
||||
"""Identity ID of the holiday."""
|
||||
name: str | None = None
|
||||
"""Name of the holiday."""
|
||||
repeat: bool | None = None
|
||||
"""Indicate whether the holiday repeats annually."""
|
||||
start_time: RFC3339Datetime | None = None
|
||||
"""Start time of the holiday"""
|
||||
end_time: RFC3339Datetime | None = None
|
||||
"""End time of the holiday"""
|
||||
|
||||
|
||||
class HolidayGroup(ForbidExtraBaseModel):
|
||||
id: HolidayGroupId
|
||||
"""Identity ID of the holiday group."""
|
||||
name: str
|
||||
"""Name of the holiday group."""
|
||||
is_default: bool
|
||||
"""Indicate whether the holiday group is the system default."""
|
||||
description: str
|
||||
"""Description of the holiday group."""
|
||||
holidays: list[Holiday] | None = None
|
||||
"""A list of the holidays within the holiday group."""
|
||||
template_name: str | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
is_private: bool | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class FetchAllHolidayGroupsResponse(ForbidExtraBaseModel):
|
||||
id: HolidayGroupId
|
||||
"""Identity ID of the holiday group."""
|
||||
name: str
|
||||
"""Name of the holiday group."""
|
||||
is_default: bool
|
||||
"""
|
||||
Indicate whether the holiday group is the system default.
|
||||
!!! note "not listed in API docs for this call"
|
||||
"""
|
||||
description: str
|
||||
"""Description of the holiday group."""
|
||||
count: int
|
||||
"""Total number of holidays"""
|
||||
is_private: bool | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class TimePeriod(ForbidExtraBaseModel):
|
||||
start_time: FormattedTime
|
||||
"""Start time of the access time period."""
|
||||
end_time: FormattedTime
|
||||
"""End time of the access time period."""
|
||||
|
||||
|
||||
class WeekSchedule(ForbidExtraBaseModel):
|
||||
sunday: list[TimePeriod]
|
||||
monday: list[TimePeriod]
|
||||
tuesday: list[TimePeriod]
|
||||
wednesday: list[TimePeriod]
|
||||
thursday: list[TimePeriod]
|
||||
friday: list[TimePeriod]
|
||||
saturday: list[TimePeriod]
|
||||
|
||||
|
||||
class _ScheduleBase(ForbidExtraBaseModel):
|
||||
id: ScheduleId
|
||||
"""Identity ID of the schedule."""
|
||||
name: str
|
||||
"""Name of the schedule."""
|
||||
is_default: bool
|
||||
"""Indicate whether the schedule is the system default."""
|
||||
type: str
|
||||
"""Contains the access type, which is assigned to a user along with an access policy."""
|
||||
|
||||
|
||||
class Schedule(_ScheduleBase):
|
||||
weekly: WeekSchedule
|
||||
"""
|
||||
The customizable scheduling strategy for each day from Sunday to Saturday.
|
||||
If not specified, it means access is allowed every day.
|
||||
"""
|
||||
holiday_schedule: list[TimePeriod]
|
||||
"""Specify the accessible period during holidays. UniFi Access Requirement: 1.20.11 or later"""
|
||||
holiday_group_id: HolidayGroupId
|
||||
"""Identity ID of the holiday group."""
|
||||
holiday_group: HolidayGroup
|
||||
|
||||
|
||||
class PartialSchedule(_ScheduleBase):
|
||||
weekly: WeekSchedule | None = None
|
||||
"""
|
||||
The customizable scheduling strategy for each day from Sunday to Saturday.
|
||||
If not specified, it means access is allowed every day.
|
||||
"""
|
||||
holiday_schedule: list[TimePeriod] | None = None
|
||||
"""Specify the accessible period during holidays. UniFi Access Requirement: 1.20.11 or later"""
|
||||
holiday_group_id: HolidayGroupId | None = None
|
||||
"""Identity ID of the holiday group."""
|
||||
holiday_group: HolidayGroup | None = None
|
||||
|
||||
|
||||
class FetchAllSchedulesResponse(_ScheduleBase):
|
||||
status: int
|
||||
"""!!! note "not listed in API docs" """
|
||||
holiday_group_id: HolidayGroupId
|
||||
"""!!! note "not listed in API docs" """
|
87
src/unifi_access/schemas/_base.py
Normal file
87
src/unifi_access/schemas/_base.py
Normal file
@ -0,0 +1,87 @@
|
||||
import datetime
|
||||
from typing import Annotated, Literal, NewType
|
||||
|
||||
from pydantic import (
|
||||
AwareDatetime,
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
PlainSerializer,
|
||||
)
|
||||
|
||||
|
||||
class ForbidExtraBaseModel(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
# TODO: these are mostly UUIDs
|
||||
AccessPolicyId = NewType("AccessPolicyId", str)
|
||||
DoorId = NewType("DoorId", str)
|
||||
FloorId = NewType("FloorId", str)
|
||||
DoorGroupId = NewType("DoorGroupId", str)
|
||||
ResourceId = DoorId | DoorGroupId
|
||||
ScheduleId = NewType("ScheduleId", str)
|
||||
UserId = NewType("UserId", str)
|
||||
UserGroupId = NewType("UserGroupId", str)
|
||||
VisitorId = NewType("VisitorId", str)
|
||||
NfcCardId = NewType("NfcCardId", str)
|
||||
HolidayId = NewType("HolidayId", str)
|
||||
HolidayGroupId = NewType("HolidayGroupId", str)
|
||||
DeviceId = NewType("DeviceId", str)
|
||||
NfcCardEnrollmentSessionId = NewType("NfcCardEnrollmentSessionId", str)
|
||||
NfcCardToken = NewType("NfcCardToken", str)
|
||||
IdentityResourceId = NewType("IdentityResourceId", str)
|
||||
|
||||
ActorId = UserId | VisitorId | DeviceId
|
||||
|
||||
UnixTimestampDateTime = Annotated[
|
||||
AwareDatetime, PlainSerializer(lambda dt: int(dt.timestamp()), return_type=int)
|
||||
]
|
||||
RFC3339Datetime = Annotated[
|
||||
AwareDatetime,
|
||||
PlainSerializer(
|
||||
lambda dt: dt.astimezone(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
return_type=str,
|
||||
),
|
||||
]
|
||||
FormattedTime = Annotated[
|
||||
datetime.time,
|
||||
PlainSerializer(lambda t: t.isoformat(timespec="seconds"), return_type=str),
|
||||
]
|
||||
|
||||
|
||||
class DoorResource(ForbidExtraBaseModel):
|
||||
type: Literal["door"] = "door"
|
||||
id: DoorId
|
||||
|
||||
|
||||
class NamedDoorResource(DoorResource):
|
||||
name: str
|
||||
|
||||
|
||||
class NamedDoorResourceWithIsBindHub(NamedDoorResource):
|
||||
is_bind_hub: bool
|
||||
|
||||
|
||||
class DoorGroupResource(ForbidExtraBaseModel):
|
||||
type: Literal["door_group"] = "door_group"
|
||||
id: DoorGroupId
|
||||
|
||||
|
||||
class NamedDoorGroupResource(DoorGroupResource):
|
||||
name: str
|
||||
|
||||
|
||||
class NamedDoorGroupResourceWithIsBindHub(NamedDoorGroupResource):
|
||||
is_bind_hub: bool
|
||||
|
||||
|
||||
Resource = DoorResource | DoorGroupResource
|
||||
NamedResource = NamedDoorResource | NamedDoorGroupResource
|
||||
NamedResourceWithIsBindHub = (
|
||||
NamedDoorResourceWithIsBindHub | NamedDoorGroupResourceWithIsBindHub
|
||||
)
|
||||
|
||||
|
||||
class PinCode(ForbidExtraBaseModel):
|
||||
token: str
|
||||
"""The user's PIN hash code credential for unlocking a door."""
|
58
src/unifi_access/schemas/_credential.py
Normal file
58
src/unifi_access/schemas/_credential.py
Normal file
@ -0,0 +1,58 @@
|
||||
from typing import Literal
|
||||
|
||||
from ._base import (
|
||||
ForbidExtraBaseModel,
|
||||
NfcCardEnrollmentSessionId,
|
||||
NfcCardId,
|
||||
NfcCardToken,
|
||||
UserId,
|
||||
)
|
||||
|
||||
|
||||
# TODO: this should be a partial of User
|
||||
class NfcCardUser(ForbidExtraBaseModel):
|
||||
id: UserId
|
||||
"""Identity ID of the user."""
|
||||
first_name: str
|
||||
"""First name of the user."""
|
||||
last_name: str
|
||||
"""Last name of the user."""
|
||||
name: str
|
||||
"""Full name of the user."""
|
||||
avatar: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class NfcCard(ForbidExtraBaseModel):
|
||||
token: NfcCardToken
|
||||
"""Identity token of the NFC card."""
|
||||
display_id: NfcCardId
|
||||
"""Display ID of the NFC card."""
|
||||
status: Literal["assigned", "pending", "disable", "deleted", "loss"]
|
||||
"""Status of the NFC card."""
|
||||
alias: str
|
||||
"""Preferred name of the NFC card."""
|
||||
card_type: str
|
||||
"""Type of the NFC card."""
|
||||
user_id: Literal[""] | UserId
|
||||
"""Owner ID of the NFC card."""
|
||||
user_type: Literal["USER", "VISITOR", ""]
|
||||
"""Type of the owner."""
|
||||
user: NfcCardUser
|
||||
"""Owner of the NFC card."""
|
||||
note: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class EnrollNfcCardResponse(ForbidExtraBaseModel):
|
||||
session_id: NfcCardEnrollmentSessionId
|
||||
"""The session for enrolling an NFC card."""
|
||||
|
||||
|
||||
class NfcCardEnrollmentStatus(ForbidExtraBaseModel):
|
||||
token: NfcCardToken
|
||||
"""Unique NFC card token."""
|
||||
id: NfcCardId
|
||||
"""Display ID of the NFC card.
|
||||
!!! note "documented as `card_id`"
|
||||
"""
|
12
src/unifi_access/schemas/_device.py
Normal file
12
src/unifi_access/schemas/_device.py
Normal file
@ -0,0 +1,12 @@
|
||||
from ._base import DeviceId, ForbidExtraBaseModel
|
||||
|
||||
|
||||
class Device(ForbidExtraBaseModel):
|
||||
id: DeviceId
|
||||
"""Identity ID of the device."""
|
||||
name: str
|
||||
"""Name of the device."""
|
||||
type: str
|
||||
"""Type of the device."""
|
||||
full_name: str
|
||||
"""Full name of the device."""
|
36
src/unifi_access/schemas/_identity.py
Normal file
36
src/unifi_access/schemas/_identity.py
Normal file
@ -0,0 +1,36 @@
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from ._base import ForbidExtraBaseModel, IdentityResourceId, UserId
|
||||
|
||||
|
||||
class IdentityInvitationUser(ForbidExtraBaseModel):
|
||||
user_id: UserId
|
||||
email: str | None = None
|
||||
|
||||
|
||||
class IdentityInvitationEmailFailure(ForbidExtraBaseModel):
|
||||
error_code: str
|
||||
error_msg: str
|
||||
user_email: str
|
||||
user_id: UserId
|
||||
|
||||
|
||||
class IdentityResourceType(StrEnum):
|
||||
EV_STATION = "ev_station"
|
||||
VPN = "vpn"
|
||||
WIFI = "wifi"
|
||||
|
||||
|
||||
class IdentityResource(ForbidExtraBaseModel):
|
||||
id: IdentityResourceId
|
||||
"""Identity ID of the resources."""
|
||||
name: str
|
||||
"""Name of the resources."""
|
||||
deleted: bool
|
||||
"""Indicate whether the resource is disabled."""
|
||||
short_name: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
# XXX: narrower type
|
||||
metadata: Any | None
|
||||
"""!!! note "not listed in API docs" """
|
104
src/unifi_access/schemas/_space.py
Normal file
104
src/unifi_access/schemas/_space.py
Normal file
@ -0,0 +1,104 @@
|
||||
from enum import StrEnum
|
||||
from typing import Generic, Literal, TypeVar
|
||||
|
||||
from ._base import (
|
||||
DoorGroupId,
|
||||
DoorId,
|
||||
FloorId,
|
||||
ForbidExtraBaseModel,
|
||||
NamedResourceWithIsBindHub,
|
||||
Resource,
|
||||
UnixTimestampDateTime,
|
||||
)
|
||||
|
||||
|
||||
class DoorGroupType(StrEnum):
|
||||
ACCESS = "access"
|
||||
BUILDING = "building"
|
||||
|
||||
|
||||
class ResourceTopology(ForbidExtraBaseModel):
|
||||
id: str
|
||||
"""Identity ID of the floor."""
|
||||
type: str
|
||||
"""Type of the floor."""
|
||||
name: str
|
||||
"""Name of the floor."""
|
||||
resources: list[NamedResourceWithIsBindHub]
|
||||
"""Contains all the doors on the floor."""
|
||||
is_bind_hub: bool | None = None
|
||||
"""
|
||||
Indicate whether the door has bound to a hub device.
|
||||
It can only # be used for remote opening if it's bound.
|
||||
"""
|
||||
|
||||
|
||||
class DoorGroupTopology(ForbidExtraBaseModel):
|
||||
# XXX: is this really optional?
|
||||
id: str | None = None
|
||||
"""Identity ID of the door group."""
|
||||
type: DoorGroupType
|
||||
"""
|
||||
The building type contains all the doors; the access type represents all the customized door groups.
|
||||
"""
|
||||
name: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
resource_topologies: list[ResourceTopology]
|
||||
"""Contains information about the floor and all its associated doors."""
|
||||
|
||||
|
||||
ResourceType = TypeVar("ResourceType", bound=Resource)
|
||||
|
||||
|
||||
class DoorGroup(ForbidExtraBaseModel, Generic[ResourceType]):
|
||||
id: DoorGroupId
|
||||
"""Identity ID of the door group."""
|
||||
name: str
|
||||
"""Name of the door group."""
|
||||
type: DoorGroupType
|
||||
"""!!! note "not listed in API docs" """
|
||||
resources: list[ResourceType]
|
||||
|
||||
|
||||
class Door(ForbidExtraBaseModel):
|
||||
id: DoorId
|
||||
"""Identity ID of the door."""
|
||||
name: str
|
||||
"""Name of the door."""
|
||||
full_name: str
|
||||
"""Full name of the door."""
|
||||
floor_id: FloorId
|
||||
"""Identity ID of the floor."""
|
||||
# XXX: is this always 'door'?
|
||||
type: Literal["door"] = "door"
|
||||
"""Type of the door."""
|
||||
is_bind_hub: bool
|
||||
"""
|
||||
Indicate whether the door has bound to a hub device.
|
||||
It can only be used for remote opening if it's bound.
|
||||
!!! note "api doc describes this as a string"
|
||||
"""
|
||||
door_lock_relay_status: Literal["lock", "unlock"]
|
||||
"""Door lock status."""
|
||||
# TODO: convert empty string to None?
|
||||
door_position_status: Literal["open", "close", ""]
|
||||
"""A null value means that no device is connected"""
|
||||
|
||||
|
||||
class DoorLockingRuleType(StrEnum):
|
||||
KEEP_LOCK = "keep_lock"
|
||||
KEEP_UNLOCK = "keep_unlock"
|
||||
CUSTOM = "custom"
|
||||
RESET = "reset"
|
||||
LOCK_EARLY = "lock_early"
|
||||
|
||||
|
||||
class DoorLockingRule(ForbidExtraBaseModel):
|
||||
type: DoorLockingRuleType
|
||||
ended_time: UnixTimestampDateTime
|
||||
|
||||
|
||||
class DoorEmergencyStatus(ForbidExtraBaseModel):
|
||||
# NOTE: the api docs list these as not required, but always include them
|
||||
lockdown: bool = False
|
||||
evacuation: bool = False
|
78
src/unifi_access/schemas/_system_log.py
Normal file
78
src/unifi_access/schemas/_system_log.py
Normal file
@ -0,0 +1,78 @@
|
||||
import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from ._base import ActorId, ForbidExtraBaseModel, UnixTimestampDateTime
|
||||
|
||||
|
||||
class SystemLogTopic(StrEnum):
|
||||
ALL = "all"
|
||||
"""All logs"""
|
||||
DOOR_OPENINGS = "door_openings"
|
||||
"""Door opening logs"""
|
||||
CRITICAL = "critical"
|
||||
"""Device restart, deletion, offline status, and detection"""
|
||||
UPDATES = "updates"
|
||||
"""Device update logs"""
|
||||
DEVICE_EVENTS = "device_events"
|
||||
"""
|
||||
Device online status, device updates, access policy synchronization,
|
||||
and active and inactive door unlock schedules
|
||||
"""
|
||||
ADMIN_ACTIVITY = "admin_activity"
|
||||
"""Admin activity, such as access policy updates, settings changes, and user management"""
|
||||
VISITOR = "visitor"
|
||||
"""Visitor-related operations"""
|
||||
|
||||
|
||||
class SystemLogEvent(ForbidExtraBaseModel):
|
||||
type: str
|
||||
display_message: str
|
||||
reason: str
|
||||
result: str
|
||||
published: UnixTimestampDateTime
|
||||
log_key: str
|
||||
|
||||
|
||||
class SystemLogActor(ForbidExtraBaseModel):
|
||||
id: Literal[""] | ActorId # XXX: check if this is complete
|
||||
type: str # TODO: at least Literal["user", "service"], maybe more
|
||||
display_name: str
|
||||
alternate_id: str # TODO: is this also an ActorId?
|
||||
alternate_name: str
|
||||
avatar: str | None = None
|
||||
sso_picture: str | None = None
|
||||
|
||||
|
||||
class SystemLogAuthentication(ForbidExtraBaseModel):
|
||||
credential_provider: str
|
||||
issuer: str
|
||||
|
||||
|
||||
# TODO: some of these could be narrowed
|
||||
class SystemLogTarget(ForbidExtraBaseModel):
|
||||
type: str
|
||||
id: str
|
||||
alternate_id: str
|
||||
alternate_name: str
|
||||
display_name: str
|
||||
|
||||
|
||||
class SystemLogSource(ForbidExtraBaseModel):
|
||||
actor: SystemLogActor
|
||||
event: SystemLogEvent
|
||||
authentication: SystemLogAuthentication | None
|
||||
target: list[SystemLogTarget]
|
||||
|
||||
|
||||
class SystemLogEntry(ForbidExtraBaseModel):
|
||||
timestamp: datetime.datetime = Field(validation_alias="@timestamp")
|
||||
id: str = Field(alias="_id")
|
||||
source: SystemLogSource = Field(alias="_source")
|
||||
tag: str
|
||||
|
||||
|
||||
class FetchSystemLogsResponse(ForbidExtraBaseModel):
|
||||
hits: list[SystemLogEntry]
|
84
src/unifi_access/schemas/_user.py
Normal file
84
src/unifi_access/schemas/_user.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""3.1 User Schemas"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from ._access_policy import AccessPolicy
|
||||
from ._base import (
|
||||
ForbidExtraBaseModel,
|
||||
NfcCardId,
|
||||
NfcCardToken,
|
||||
PinCode,
|
||||
UnixTimestampDateTime,
|
||||
UserGroupId,
|
||||
UserId,
|
||||
)
|
||||
|
||||
|
||||
class UserStatus(StrEnum):
|
||||
ACTIVE = "ACTIVE"
|
||||
"""The user account is in active status."""
|
||||
PENDING = "PENDING"
|
||||
"""A new admin account has been invited by the SSO account, but the invitation has not been accepted."""
|
||||
DEACTIVATED = "DEACTIVATED"
|
||||
"""The account has been deactivated."""
|
||||
|
||||
|
||||
# TODO: should be a partial of NfcCard
|
||||
class UserNfcCard(ForbidExtraBaseModel):
|
||||
id: NfcCardId
|
||||
"""Display ID of the NFC card."""
|
||||
token: NfcCardToken
|
||||
"""Unique NFC card token."""
|
||||
type: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class User(ForbidExtraBaseModel):
|
||||
id: UserId
|
||||
"""Identity ID of the user."""
|
||||
first_name: str
|
||||
"""First name of the user."""
|
||||
last_name: str
|
||||
"""Last name of the user."""
|
||||
full_name: str
|
||||
"""Full name of the user."""
|
||||
alias: str
|
||||
"""Preferred name of the user."""
|
||||
user_email: str
|
||||
"""Email of the user."""
|
||||
email: str
|
||||
"""!!! note "not listed in API docs, Unclear how this is different from `user_email`" """
|
||||
email_status: str
|
||||
"""The status of the user's email."""
|
||||
username: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
avatar_relative_path: str
|
||||
"""!!! note "not listed in API docs" """
|
||||
phone: str
|
||||
"""Contact phone number of the user."""
|
||||
employee_number: str
|
||||
"""Employee number of the user."""
|
||||
onboard_time: UnixTimestampDateTime
|
||||
"""User onboarding date."""
|
||||
status: UserStatus
|
||||
|
||||
|
||||
class FullUser(User):
|
||||
nfc_cards: list[UserNfcCard]
|
||||
"""Token associated with the bound NFC card."""
|
||||
pin_code: PinCode | None
|
||||
"""Token associated with the bound PIN code."""
|
||||
access_policy_ids: list[str] | None = None
|
||||
"""Collection of the access policy ID."""
|
||||
access_policies: list[AccessPolicy] | None = None
|
||||
"""All policies assigned to the user."""
|
||||
|
||||
|
||||
class UserGroup(ForbidExtraBaseModel):
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
id: UserGroupId
|
||||
full_name: str
|
||||
name: str
|
||||
up_id: UserGroupId
|
||||
up_ids: list[UserGroupId]
|
91
src/unifi_access/schemas/_visitor.py
Normal file
91
src/unifi_access/schemas/_visitor.py
Normal file
@ -0,0 +1,91 @@
|
||||
from enum import StrEnum
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BeforeValidator
|
||||
|
||||
from unifi_access.schemas import UserNfcCard
|
||||
from unifi_access.schemas._access_policy import PartialSchedule
|
||||
|
||||
from ._base import (
|
||||
ForbidExtraBaseModel,
|
||||
NamedResource,
|
||||
PinCode,
|
||||
ScheduleId,
|
||||
UnixTimestampDateTime,
|
||||
UserId,
|
||||
VisitorId,
|
||||
)
|
||||
|
||||
|
||||
class VisitorStatus(StrEnum):
|
||||
UPCOMING = "UPCOMING"
|
||||
VISITED = "VISITED"
|
||||
VISITING = "VISITING"
|
||||
CANCELLED = "CANCELLED"
|
||||
NO_VISIT = "NO_VISIT"
|
||||
ACTIVE = "ACTIVE"
|
||||
|
||||
|
||||
class VisitReason(StrEnum):
|
||||
INTERVIEW = "Interview"
|
||||
BUSINESS = "Business"
|
||||
COOPERATION = "Cooperation"
|
||||
OTHERS = "Others"
|
||||
|
||||
|
||||
# TODO: some of these might not be nullable from some endpoints
|
||||
# Generic to avoid duplication. `fetch_visitor` returns less info in schedule than `get_all_visitors`.
|
||||
class Visitor(ForbidExtraBaseModel):
|
||||
id: VisitorId
|
||||
"""Identity ID of the visitor."""
|
||||
first_name: str
|
||||
"""First name of the visitor."""
|
||||
last_name: str
|
||||
"""Last name of the visitor."""
|
||||
status: VisitorStatus
|
||||
"""The visitor's status."""
|
||||
inviter_id: UserId | None = None
|
||||
"""Identity ID of the inviter."""
|
||||
inviter_name: str | None = None
|
||||
"""Name of the inviter."""
|
||||
mobile_phone: str | None = None
|
||||
"""Contact phone number of the visitor."""
|
||||
remarks: str | None = None
|
||||
"""Remarks of the visitor."""
|
||||
email: str | None = None
|
||||
"""Email of the visitor."""
|
||||
visitor_company: str | None = None
|
||||
"""Company of the visitor."""
|
||||
visit_reason: VisitReason | None = None
|
||||
"""Visit reason"""
|
||||
start_time: UnixTimestampDateTime | None = None
|
||||
"""Start time of the visit."""
|
||||
end_time: UnixTimestampDateTime | None = None
|
||||
"""End time of the visit."""
|
||||
nfc_cards: list[UserNfcCard]
|
||||
|
||||
# coerce empty dict to None, because for some reason that is what `fetch_visitor` returns
|
||||
pin_code: Annotated[PinCode | None, BeforeValidator(lambda v: v or None)] = None
|
||||
schedule_id: ScheduleId
|
||||
"""Identity ID of the schedule."""
|
||||
schedule: PartialSchedule | None = None
|
||||
"""
|
||||
The schedule assigned to the visitor.
|
||||
If the schedule information is present, it indicates that the visit schedule is recurring.
|
||||
If the schedule information is not included, it indicates a one-time visit schedule.
|
||||
"""
|
||||
resources: list[NamedResource] | None = None
|
||||
"""Specify the locations that the visitor can access."""
|
||||
avatar: str | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
location_id: str | None = None
|
||||
"""!!! note "not listed in API docs" """
|
||||
|
||||
|
||||
class FetchAllVisitorsExpansion(StrEnum):
|
||||
NONE = "none"
|
||||
ACCESS_POLICY = "access_policy"
|
||||
RESOURCE = "resource"
|
||||
SCHEDULE = "schedule"
|
||||
NFC_CARD = "nfc_card"
|
||||
PIN_CODE = "pin_code"
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
30
tests/base.py
Normal file
30
tests/base.py
Normal file
@ -0,0 +1,30 @@
|
||||
import unittest
|
||||
|
||||
import unifi_access
|
||||
|
||||
|
||||
class UnifiAccessTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.host = "localhost:12445"
|
||||
self.api_token = "EXAMPLE_TOKEN"
|
||||
self.common_headers = {
|
||||
"Authorization": f"Bearer {self.api_token}",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
self.client = unifi_access.AccessClient(
|
||||
host=self.host, api_token=self.api_token
|
||||
)
|
||||
|
||||
# XXX: TODO:
|
||||
# 11 Notification
|
||||
# - 11.1 Fetch Notifications [WebSocket]
|
||||
# - 11.2 List of Supported Webhook Events [Webhook]
|
||||
# - 11.3 Fetch Webhook Endpoints List [Webhook]
|
||||
# - 11.4 Add Webhook Endpoints [Webhook]
|
||||
# - 11.5 Update Webhook Endpoints [Webhook]
|
||||
# - 11.6 Delete Webhook Endpoints [Webhook]
|
||||
# - 11.7 Allow Webhook Endpoint Owner to Receive Webhook Events [Webhook]
|
||||
# 12 API Server
|
||||
# - 12.1 Upload HTTPS Certificate
|
||||
# - 12.2 Delete HTTPS Certificate
|
22
tests/conftest.py
Normal file
22
tests/conftest.py
Normal file
@ -0,0 +1,22 @@
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
from unifi_access import AccessClient
|
||||
|
||||
host = "localhost:12445"
|
||||
api_token = "EXAMPLE_TOKEN"
|
||||
|
||||
|
||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
parser.addoption("--live-access-host", help="Live Unifi Access Host")
|
||||
parser.addoption("--live-access-api-token", help="Live Unifi Access Host")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def live_access_client(request: pytest.FixtureRequest) -> AccessClient:
|
||||
return AccessClient(
|
||||
cast(str, request.config.getoption("--live-access-host", skip=True)),
|
||||
cast(str, request.config.getoption("--live-access-api-token", skip=True)),
|
||||
verify=False,
|
||||
)
|
0
tests/live/__init__.py
Normal file
0
tests/live/__init__.py
Normal file
87
tests/live/test_access_policy.py
Normal file
87
tests/live/test_access_policy.py
Normal file
@ -0,0 +1,87 @@
|
||||
from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from unifi_access import AccessClient
|
||||
from unifi_access.schemas import AccessPolicy, HolidayGroup, Schedule, WeekSchedule
|
||||
from unifi_access.schemas._base import DoorResource
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def holiday_group(live_access_client: AccessClient) -> Generator[HolidayGroup]:
|
||||
_holiday_group = (
|
||||
live_access_client.create_holiday_group("Test Holiday Group")
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
yield _holiday_group
|
||||
live_access_client.delete_holiday_group(_holiday_group.id)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def schedule(
|
||||
live_access_client: AccessClient, holiday_group: HolidayGroup
|
||||
) -> Generator[Schedule]:
|
||||
_schedule = (
|
||||
live_access_client.create_schedule(
|
||||
"Test Schedule",
|
||||
week_schedule=WeekSchedule(
|
||||
sunday=[],
|
||||
monday=[],
|
||||
tuesday=[],
|
||||
wednesday=[],
|
||||
thursday=[],
|
||||
friday=[],
|
||||
saturday=[],
|
||||
),
|
||||
holiday_group_id=holiday_group.id,
|
||||
)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
yield _schedule
|
||||
live_access_client.delete_schedule(_schedule.id)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def access_policy(
|
||||
live_access_client: AccessClient, schedule: Schedule
|
||||
) -> Generator[AccessPolicy]:
|
||||
_access_policy = (
|
||||
live_access_client.create_access_policy("Test Access Policy", schedule.id)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
yield _access_policy
|
||||
live_access_client.delete_access_policy(_access_policy.id)
|
||||
|
||||
|
||||
def test_access_policy_lifecycle(
|
||||
live_access_client: AccessClient,
|
||||
holiday_group: HolidayGroup,
|
||||
schedule: Schedule,
|
||||
access_policy: AccessPolicy,
|
||||
) -> None:
|
||||
assert access_policy
|
||||
doors = live_access_client.fetch_all_doors().success_or_raise().data
|
||||
resources = [DoorResource(id=door.id) for door in doors]
|
||||
updated_access_policy = (
|
||||
live_access_client.update_access_policy(access_policy.id, resources=resources)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
assert updated_access_policy.resources == resources
|
||||
|
||||
updated_holiday_group = (
|
||||
live_access_client.update_holiday_group(
|
||||
holiday_group.id, description="Test Description"
|
||||
)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
assert updated_holiday_group.description == "Test Description"
|
||||
|
||||
holiday_groups = (
|
||||
live_access_client.fetch_all_holiday_groups().success_or_raise().data
|
||||
)
|
||||
assert any(g.id == updated_holiday_group.id for g in holiday_groups)
|
128
tests/live/test_user_lifecycle.py
Normal file
128
tests/live/test_user_lifecycle.py
Normal file
@ -0,0 +1,128 @@
|
||||
import functools
|
||||
from collections.abc import Generator
|
||||
from typing import Protocol
|
||||
|
||||
import pytest
|
||||
|
||||
from unifi_access import AccessClient
|
||||
from unifi_access.schemas import User, UserGroup, UserGroupId, UserStatus
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def user(live_access_client: AccessClient) -> User:
|
||||
# XXX: maybe should test email and employeeNumber, but they are
|
||||
# unique on the server, and I can't delete users. Maybe
|
||||
# should fetch user if it already exists?
|
||||
# TODO: add an onboard time?
|
||||
return live_access_client.register_user("Test", "User").success_or_raise().data
|
||||
|
||||
|
||||
def test_user_lifecycle(live_access_client: AccessClient, user: User) -> None:
|
||||
assert user.id
|
||||
assert user.first_name == "Test"
|
||||
assert user.last_name == "User"
|
||||
assert user.status == UserStatus.ACTIVE
|
||||
|
||||
# Fetch user
|
||||
fetched_user = live_access_client.fetch_user(user.id).success_or_raise().data
|
||||
assert fetched_user.id == user.id
|
||||
assert fetched_user.first_name == "Test"
|
||||
assert fetched_user.last_name == "User"
|
||||
assert fetched_user.status == UserStatus.ACTIVE
|
||||
|
||||
# Update user
|
||||
live_access_client.update_user(
|
||||
user.id, last_name="Updated User", status=UserStatus.DEACTIVATED
|
||||
).success_or_raise()
|
||||
|
||||
# Fetch again, and check changes
|
||||
fetched_user = live_access_client.fetch_user(user.id).success_or_raise().data
|
||||
assert fetched_user.id == user.id
|
||||
assert fetched_user.first_name == "Test"
|
||||
assert fetched_user.last_name == "Updated User"
|
||||
assert fetched_user.status == UserStatus.DEACTIVATED
|
||||
|
||||
# Check for the user in full user list
|
||||
# TODO: test pagination
|
||||
all_users = live_access_client.fetch_all_users().success_or_raise().data
|
||||
matching_user = next(u for u in all_users if u.id == user.id)
|
||||
assert matching_user.id == user.id
|
||||
assert matching_user.first_name == "Test"
|
||||
assert matching_user.last_name == "Updated User"
|
||||
assert matching_user.status == UserStatus.DEACTIVATED
|
||||
|
||||
|
||||
class UserGroupFactory(Protocol):
|
||||
def __call__(
|
||||
self, name: str, up_id: UserGroupId | None = None
|
||||
) -> UserGroup | None: ...
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_user_group(
|
||||
live_access_client: AccessClient,
|
||||
) -> Generator[UserGroupFactory]:
|
||||
created_groups = []
|
||||
|
||||
@functools.wraps(live_access_client.create_user_group)
|
||||
def _make_user_group(
|
||||
name: str,
|
||||
up_id: UserGroupId | None = None,
|
||||
) -> UserGroup | None:
|
||||
group = (
|
||||
live_access_client.create_user_group(name, up_id).success_or_raise().data
|
||||
)
|
||||
created_groups.append(group)
|
||||
return group
|
||||
|
||||
yield _make_user_group
|
||||
|
||||
for group in reversed(created_groups):
|
||||
if group is not None:
|
||||
live_access_client.delete_user_group(group.id)
|
||||
|
||||
|
||||
def test_user_groups_lifecycle(
|
||||
live_access_client: AccessClient,
|
||||
make_user_group: UserGroupFactory,
|
||||
user: User,
|
||||
) -> None:
|
||||
# Create group
|
||||
group = make_user_group("Test Group")
|
||||
assert group, "group 'Test Group' already existed, please delete"
|
||||
|
||||
# Fetch group
|
||||
fetched_group = (
|
||||
live_access_client.fetch_user_group(group.id).success_or_raise().data
|
||||
)
|
||||
assert fetched_group.name == "Test Group"
|
||||
|
||||
# Check for group in in full group list
|
||||
groups = live_access_client.fetch_all_user_groups().success_or_raise().data
|
||||
matching_group = next(g for g in groups if g.id == group.id)
|
||||
assert matching_group
|
||||
|
||||
# Create child group
|
||||
child_group = make_user_group("Test Group Child", group.id)
|
||||
assert child_group, "group 'Test Group / Test Group Child' already existed, which shouldn't be possible"
|
||||
assert child_group.up_id == group.id
|
||||
assert child_group.up_ids[-1] == group.id
|
||||
|
||||
# Add user to group
|
||||
live_access_client.assign_user_to_user_group(group.id, [user.id])
|
||||
|
||||
# Fetch users in the group
|
||||
users_in_group = (
|
||||
live_access_client.fetch_users_in_a_user_group(group.id).success_or_raise().data
|
||||
)
|
||||
assert len(users_in_group) == 1
|
||||
assert users_in_group[0].id == user.id
|
||||
|
||||
# Fetch all users in the group
|
||||
all_users_in_group = (
|
||||
live_access_client.fetch_all_users_in_a_user_group(group.id)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
assert len(all_users_in_group) == 1
|
||||
assert all_users_in_group[0].id == user.id
|
104
tests/live/test_visitor_lifecycle.py
Normal file
104
tests/live/test_visitor_lifecycle.py
Normal file
@ -0,0 +1,104 @@
|
||||
import datetime
|
||||
import functools
|
||||
from collections.abc import Callable, Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from unifi_access import AccessClient
|
||||
from unifi_access.schemas import FetchAllVisitorsExpansion, Visitor, VisitReason
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_visitor(live_access_client: AccessClient) -> Generator[Callable[..., Visitor]]:
|
||||
created_visitors: list[Visitor] = []
|
||||
|
||||
@functools.wraps(live_access_client.create_visitor)
|
||||
def _make_visitor(*args, **kwargs) -> Visitor:
|
||||
visitor = (
|
||||
live_access_client.create_visitor(*args, **kwargs).success_or_raise().data
|
||||
)
|
||||
created_visitors.append(visitor)
|
||||
return visitor
|
||||
|
||||
yield _make_visitor
|
||||
|
||||
for visitor in created_visitors:
|
||||
live_access_client.delete_visitor(visitor.id, is_force=True)
|
||||
|
||||
|
||||
def test_visitor_lifecycle(
|
||||
live_access_client: AccessClient, make_visitor: Callable[..., Visitor]
|
||||
) -> None:
|
||||
now = datetime.datetime.now(tz=datetime.UTC)
|
||||
visitor = make_visitor(
|
||||
"Test",
|
||||
"Visitor",
|
||||
start_time=now + datetime.timedelta(days=1),
|
||||
end_time=now + datetime.timedelta(days=2),
|
||||
visit_reason=VisitReason.BUSINESS,
|
||||
)
|
||||
assert visitor.first_name == "Test"
|
||||
|
||||
fetched_visitor = (
|
||||
live_access_client.fetch_visitor(visitor.id).success_or_raise().data
|
||||
)
|
||||
assert fetched_visitor.first_name == "Test"
|
||||
assert fetched_visitor.pin_code is None
|
||||
|
||||
# pin code assign/unassign
|
||||
pin_code = live_access_client.generate_pin_code().success_or_raise().data
|
||||
live_access_client.assign_pin_code_to_visitor(
|
||||
visitor.id, pin_code
|
||||
).success_or_raise()
|
||||
fetched_visitor = (
|
||||
live_access_client.fetch_visitor(visitor.id).success_or_raise().data
|
||||
)
|
||||
assert fetched_visitor.pin_code
|
||||
assert fetched_visitor.pin_code.token
|
||||
live_access_client.unassign_pin_code_from_visitor(visitor.id).success_or_raise()
|
||||
fetched_visitor = (
|
||||
live_access_client.fetch_visitor(visitor.id).success_or_raise().data
|
||||
)
|
||||
assert fetched_visitor.pin_code is None
|
||||
|
||||
updated_visitor = (
|
||||
live_access_client.update_visitor(visitor.id, first_name="Updated Test")
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
assert updated_visitor.first_name == "Updated Test"
|
||||
|
||||
all_visitors = live_access_client.fetch_all_visitors().success_or_raise().data
|
||||
matched_visitor = next(v for v in all_visitors if v.id == visitor.id)
|
||||
assert matched_visitor.first_name == "Updated Test"
|
||||
|
||||
expanded_all_visitors = (
|
||||
live_access_client.fetch_all_visitors(
|
||||
expand=[
|
||||
FetchAllVisitorsExpansion.ACCESS_POLICY,
|
||||
FetchAllVisitorsExpansion.RESOURCE,
|
||||
FetchAllVisitorsExpansion.SCHEDULE,
|
||||
FetchAllVisitorsExpansion.NFC_CARD,
|
||||
FetchAllVisitorsExpansion.PIN_CODE,
|
||||
]
|
||||
)
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
expanded_matched_visitor = next(
|
||||
v for v in expanded_all_visitors if v.id == visitor.id
|
||||
)
|
||||
assert expanded_matched_visitor.first_name == "Updated Test"
|
||||
# TODO: test expanded contents
|
||||
|
||||
non_expanded_all_visitors = (
|
||||
live_access_client.fetch_all_visitors(expand=[FetchAllVisitorsExpansion.NONE])
|
||||
.success_or_raise()
|
||||
.data
|
||||
)
|
||||
non_expanded_matched_visitor = next(
|
||||
v for v in non_expanded_all_visitors if v.id == visitor.id
|
||||
)
|
||||
assert non_expanded_matched_visitor.first_name == "Updated Test"
|
||||
assert non_expanded_matched_visitor.schedule is None
|
||||
# TODO: test other non-expanded contents
|
869
tests/test_access_policy.py
Normal file
869
tests/test_access_policy.py
Normal file
@ -0,0 +1,869 @@
|
||||
import datetime
|
||||
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import (
|
||||
AccessPolicyId,
|
||||
DoorGroupId,
|
||||
DoorGroupResource,
|
||||
DoorId,
|
||||
DoorResource,
|
||||
HolidayGroupId,
|
||||
HolidayId,
|
||||
PartialHoliday,
|
||||
ScheduleId,
|
||||
TimePeriod,
|
||||
WeekSchedule,
|
||||
)
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class AccessPolicyTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_create_access_policy(self) -> None:
|
||||
"""5.2 Create Access Policy"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/access_policies",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "test",
|
||||
"resource": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "4e108aeb-ec9a-4822-bf86-170ea986f934",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "bb5eb965-42dc-4206-9654-88a2d1c3aaa5",
|
||||
"name": "test",
|
||||
"resources": [
|
||||
{"id": "6ff875d2-af87-470b-9cb5-774c6596afc8", "type": "door"},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "4e108aeb-ec9a-4822-bf86-170ea986f934",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.create_access_policy(
|
||||
name="test",
|
||||
resources=[
|
||||
DoorResource(
|
||||
id=DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("5c496423-6d25-4e4f-8cdf-95ad5135188a"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("d5573467-d6b3-4e8f-8e48-8a322b91664a"),
|
||||
),
|
||||
],
|
||||
schedule_id=ScheduleId("4e108aeb-ec9a-4822-bf86-170ea986f934"),
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "bb5eb965-42dc-4206-9654-88a2d1c3aaa5"
|
||||
|
||||
@responses.activate
|
||||
def test_update_access_policy(self) -> None:
|
||||
"""5.3 Update Access Policy"""
|
||||
access_policy_id = AccessPolicyId("242c88e3-0524-42de-8447-45891c5df714")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/{access_policy_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "test",
|
||||
"resource": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "4e108aeb-ec9a-4822-bf86-170ea986f934",
|
||||
}
|
||||
),
|
||||
],
|
||||
# NOTE: no example provided in docs, using the same one as `create`
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "bb5eb965-42dc-4206-9654-88a2d1c3aaa5",
|
||||
"name": "test",
|
||||
"resources": [
|
||||
{"id": "6ff875d2-af87-470b-9cb5-774c6596afc8", "type": "door"},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "4e108aeb-ec9a-4822-bf86-170ea986f934",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.update_access_policy(
|
||||
access_policy_id=access_policy_id,
|
||||
name="test",
|
||||
resources=[
|
||||
DoorResource(
|
||||
id=DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("5c496423-6d25-4e4f-8cdf-95ad5135188a"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("d5573467-d6b3-4e8f-8e48-8a322b91664a"),
|
||||
),
|
||||
],
|
||||
schedule_id=ScheduleId("4e108aeb-ec9a-4822-bf86-170ea986f934"),
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "bb5eb965-42dc-4206-9654-88a2d1c3aaa5"
|
||||
|
||||
@responses.activate
|
||||
def test_delete_access_policy(self) -> None:
|
||||
"""5.4 Delete Access Policy"""
|
||||
access_policy_id = AccessPolicyId("60d0bcc-5d4f-4e7b-8a3c-8d4502765e11")
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/{access_policy_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "msg": "success", "data": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_access_policy(
|
||||
access_policy_id=access_policy_id
|
||||
).success_or_raise()
|
||||
|
||||
assert resp.data == "success"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_access_policy(self) -> None:
|
||||
"""5.5 Fetch Access Policy"""
|
||||
access_policy_id = AccessPolicyId("ed09985f-cf52-486e-bc33-377b6ed7bbf2")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/{access_policy_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "ed09985f-cf52-486e-bc33-377b6ed7bbf2",
|
||||
"name": "test11",
|
||||
"resources": [
|
||||
{"id": "6ff875d2-af87-470b-9cb5-774c6596afc8", "type": "door"},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "4e108aeb-ec9a-4822-bf86-170ea986f934",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_access_policy(
|
||||
access_policy_id=access_policy_id
|
||||
).success_or_raise()
|
||||
|
||||
assert resp.data.id == "ed09985f-cf52-486e-bc33-377b6ed7bbf2"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_access_policies(self) -> None:
|
||||
"""5.6 Fetch All Access Policies"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "73f15cab-c725-4a76-a419-a4026d131e96",
|
||||
"name": "Default Admin Policy",
|
||||
"resources": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "73facd6c-839e-4521-a4f4-c07e1d44e748",
|
||||
},
|
||||
{
|
||||
"id": "b96948a4-fed9-40a3-9c4a-e473822a3db7",
|
||||
"name": "Default UNVR Policy",
|
||||
"resources": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
],
|
||||
"schedule_id": "58c0f89b-f35c-4d2c-af7b-8b8918df2c31",
|
||||
},
|
||||
{
|
||||
"id": "edbc80df-3698-49fd-8b53-f1867f104947",
|
||||
"name": "TEST",
|
||||
"resources": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
],
|
||||
"schedule_id": "73facd6c-839e-4521-a4f4-c07e1d44e748",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_access_policies().success_or_raise()
|
||||
assert resp.data[0].id == "73f15cab-c725-4a76-a419-a4026d131e96"
|
||||
|
||||
@responses.activate
|
||||
def test_create_holiday_group(self) -> None:
|
||||
"""5.8 Create Holiday Group"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/holiday_groups",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "Holiday Group-169286791557142",
|
||||
"holidays": [
|
||||
{
|
||||
"name": "Holiday Name 1",
|
||||
"description": "",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25T00:00:00Z",
|
||||
"end_time": "2023-08-26T00:00:00Z",
|
||||
},
|
||||
{
|
||||
"name": "Holiday Name 2",
|
||||
"description": "",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26T00:00:00Z",
|
||||
"end_time": "2023-08-27T00:00:00Z",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"description": "",
|
||||
"holidays": [
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-26 00:00:00Z",
|
||||
"id": "8900533d-03be-4f84-832d-54ff59905759",
|
||||
"name": "Holiday Name 1",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25 00:00:00Z",
|
||||
},
|
||||
{
|
||||
# NOTE: duplicated key in sample data
|
||||
# "name": "holiday-2023-08-26",
|
||||
"end_time": "2023-08-27 00:00:00Z",
|
||||
"id": "9fff81cc-d476-40c4-80f9-d510451ce2cd",
|
||||
"name": "Holiday Name 2",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26 00:00:00Z",
|
||||
},
|
||||
],
|
||||
"id": "7be7a7a0-818f-4f76-98c3-1c38957f4dca",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-169286791557142",
|
||||
"template_name": "",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.create_holiday_group(
|
||||
name="Holiday Group-169286791557142",
|
||||
holidays=[
|
||||
PartialHoliday(
|
||||
name="Holiday Name 1",
|
||||
description="",
|
||||
repeat=False,
|
||||
start_time=datetime.datetime(2023, 8, 25, tzinfo=datetime.UTC),
|
||||
end_time=datetime.datetime(2023, 8, 26, tzinfo=datetime.UTC),
|
||||
),
|
||||
PartialHoliday(
|
||||
name="Holiday Name 2",
|
||||
description="",
|
||||
repeat=False,
|
||||
start_time=datetime.datetime(2023, 8, 26, tzinfo=datetime.UTC),
|
||||
end_time=datetime.datetime(2023, 8, 27, tzinfo=datetime.UTC),
|
||||
),
|
||||
],
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "7be7a7a0-818f-4f76-98c3-1c38957f4dca"
|
||||
|
||||
@responses.activate
|
||||
def test_update_holiday_group(self) -> None:
|
||||
"""5.9 Update Holiday Group"""
|
||||
holiday_group_id = HolidayGroupId("7be7a7a0-818f-4f76-98c3-1c38957f4dca")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/holiday_groups/{holiday_group_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "Holiday Group-169286791557142",
|
||||
"holidays": [
|
||||
# add a new holiday
|
||||
{
|
||||
"name": "Holiday Name 1",
|
||||
"description": "",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25T00:00:00Z",
|
||||
"end_time": "2023-08-26T00:00:00Z",
|
||||
},
|
||||
# update an existing holiday
|
||||
{
|
||||
"id": "d23a4226-765f-4967-b84f-6dfd53f33c89",
|
||||
"name": "Holiday Name 2",
|
||||
"description": "",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26T00:00:00Z",
|
||||
"end_time": "2023-08-27T00:00:00Z",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"description": "",
|
||||
"holidays": [
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-26 00:00:00Z",
|
||||
"id": "8900533d-03be-4f84-832d-54ff59905759",
|
||||
"name": "Holiday Name 1",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25 00:00:00Z",
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-27 00:00:00Z",
|
||||
"id": "9fff81cc-d476-40c4-80f9-d510451ce2cd",
|
||||
"name": "Holiday Name 2",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26 00:00:00Z",
|
||||
},
|
||||
],
|
||||
"id": "7be7a7a0-818f-4f76-98c3-1c38957f4dca",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-169286791557142",
|
||||
"template_name": "",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.update_holiday_group(
|
||||
holiday_group_id=holiday_group_id,
|
||||
name="Holiday Group-169286791557142",
|
||||
holidays=[
|
||||
# add a new holiday
|
||||
PartialHoliday(
|
||||
name="Holiday Name 1",
|
||||
description="",
|
||||
repeat=False,
|
||||
start_time=datetime.datetime(2023, 8, 25, tzinfo=datetime.UTC),
|
||||
end_time=datetime.datetime(2023, 8, 26, tzinfo=datetime.UTC),
|
||||
),
|
||||
# update an existing holiday
|
||||
PartialHoliday(
|
||||
id=HolidayId("d23a4226-765f-4967-b84f-6dfd53f33c89"),
|
||||
name="Holiday Name 2",
|
||||
description="",
|
||||
repeat=False,
|
||||
start_time=datetime.datetime(2023, 8, 26, tzinfo=datetime.UTC),
|
||||
end_time=datetime.datetime(2023, 8, 27, tzinfo=datetime.UTC),
|
||||
),
|
||||
],
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "7be7a7a0-818f-4f76-98c3-1c38957f4dca"
|
||||
|
||||
@responses.activate
|
||||
def test_delete_holiday_group(self) -> None:
|
||||
"""5.10 Delete Holiday Group"""
|
||||
holiday_group_id = HolidayGroupId("7be7a7a0-818f-4f76-98c3-1c38957f4dca")
|
||||
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/holiday_groups/{holiday_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "msg": "success", "data": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_holiday_group(
|
||||
holiday_group_id=holiday_group_id
|
||||
).success_or_raise()
|
||||
|
||||
assert resp.data == "success"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_holiday_group(self) -> None:
|
||||
"""5.11 Fetch Holiday Group"""
|
||||
holiday_group_id = HolidayGroupId("7be7a7a0-818f-4f76-98c3-1c38957f4dca")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/holiday_groups/{holiday_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"description": "",
|
||||
"holidays": [
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-26 00:00:00Z",
|
||||
"id": "8900533d-03be-4f84-832d-54ff59905759",
|
||||
"name": "Holiday Name 1",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25 00:00:00Z",
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-27 00:00:00Z",
|
||||
"id": "9fff81cc-d476-40c4-80f9-d510451ce2cd",
|
||||
"name": "Holiday Name 2",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26 00:00:00Z",
|
||||
},
|
||||
],
|
||||
"id": "7be7a7a0-818f-4f76-98c3-1c38957f4dca",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-169286791557142",
|
||||
"template_name": "",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_holiday_group(
|
||||
holiday_group_id=holiday_group_id
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "7be7a7a0-818f-4f76-98c3-1c38957f4dca"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_holiday_groups(self) -> None:
|
||||
"""5.12 Fetch All Holiday Groups"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/holiday_groups",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"count": 0,
|
||||
"description": "",
|
||||
"id": "8cc22b49-a7f4-49a6-9f04-044444992d6c",
|
||||
"is_default": True,
|
||||
"name": "No Holidays",
|
||||
},
|
||||
{
|
||||
"count": 2,
|
||||
"description": "",
|
||||
"id": "86c634da-7b2c-411c-a2c1-1495d089c719",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-1692867312225",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_holiday_groups().success_or_raise()
|
||||
assert resp.data[0].id == "8cc22b49-a7f4-49a6-9f04-044444992d6c"
|
||||
|
||||
@responses.activate
|
||||
def test_create_schedule(self) -> None:
|
||||
"""5.14 Create Schedule"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/schedules",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "schedule-1688977094169",
|
||||
"week_schedule": {
|
||||
"sunday": [],
|
||||
"monday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"tuesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"wednesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"thursday": [],
|
||||
"friday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"saturday": [],
|
||||
},
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"holiday_schedule": [
|
||||
{"start_time": "03:15:00", "end_time": "11:45:59"},
|
||||
{"start_time": "15:00:00", "end_time": "19:00:59"},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "1d31b648-b8ff-4bd1-b742-60dbd70592cd",
|
||||
"is_default": False,
|
||||
"name": "schedule-1688977094169",
|
||||
"type": "access",
|
||||
"weekly": {
|
||||
"friday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"monday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"saturday": [],
|
||||
"sunday": [],
|
||||
"thursday": [],
|
||||
"tuesday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"wednesday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
},
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"holiday_group": {
|
||||
"description": "",
|
||||
"holidays": [
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-26 00:00:00Z",
|
||||
"id": "d51777c4-9559-45aa-8e23-434995d9d2a1",
|
||||
"is_template": False,
|
||||
"name": "Holiday Name 1",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25 00:00:00Z",
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-27 00:00:00Z",
|
||||
"id": "d23a4226-765f-4967-b84f-6dfd53f33c89",
|
||||
"is_template": False,
|
||||
"name": "Holiday Name 2",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26 00:00:00Z",
|
||||
},
|
||||
],
|
||||
"id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-1692867915571423",
|
||||
"template_name": "",
|
||||
},
|
||||
"holiday_schedule": [
|
||||
{"start_time": "03:15:00", "end_time": "11:45:59"},
|
||||
{"start_time": "15:00:00", "end_time": "19:00:59"},
|
||||
],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.create_schedule(
|
||||
name="schedule-1688977094169",
|
||||
week_schedule=WeekSchedule(
|
||||
sunday=[],
|
||||
monday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
tuesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
wednesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
thursday=[],
|
||||
friday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
saturday=[],
|
||||
),
|
||||
holiday_group_id=HolidayGroupId("75660081-431b-4dbe-9b98-e0257877118e"),
|
||||
holiday_schedule=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(3, 15, 0),
|
||||
end_time=datetime.time(11, 45, 59),
|
||||
),
|
||||
TimePeriod(
|
||||
start_time=datetime.time(15, 0, 0),
|
||||
end_time=datetime.time(19, 0, 59),
|
||||
),
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_update_schedule(self) -> None:
|
||||
"""5.15 Update Schedule"""
|
||||
schedule_id = ScheduleId("1d31b648-b8ff-4bd1-b742-60dbd70592cd")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/schedules/{schedule_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "schedule-1688977094169",
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"week_schedule": {
|
||||
"sunday": [],
|
||||
"monday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"tuesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"wednesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"thursday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:01:59"}
|
||||
],
|
||||
"friday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"saturday": [],
|
||||
},
|
||||
"holiday_schedule": [
|
||||
{"start_time": "03:15:00", "end_time": "11:45:59"}
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": {}, "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.update_schedule(
|
||||
schedule_id=schedule_id,
|
||||
name="schedule-1688977094169",
|
||||
holiday_group_id=HolidayGroupId("75660081-431b-4dbe-9b98-e0257877118e"),
|
||||
week_schedule=WeekSchedule(
|
||||
sunday=[],
|
||||
monday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
tuesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
wednesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
thursday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 1, 59),
|
||||
)
|
||||
],
|
||||
friday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
saturday=[],
|
||||
),
|
||||
holiday_schedule=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(3, 15, 0),
|
||||
end_time=datetime.time(11, 45, 59),
|
||||
)
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_schedule(self) -> None:
|
||||
"""5.16 Fetch Schedule"""
|
||||
schedule_id = ScheduleId("1d31b648-b8ff-4bd1-b742-60dbd70592cd")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/schedules/{schedule_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "1d31b648-b8ff-4bd1-b742-60dbd70592cd",
|
||||
"is_default": False,
|
||||
"name": "schedule-1688977094169",
|
||||
"type": "access",
|
||||
"weekly": {
|
||||
"friday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"monday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"saturday": [],
|
||||
"sunday": [],
|
||||
"thursday": [
|
||||
{"end_time": "17:01:59", "start_time": "10:00:00"}
|
||||
],
|
||||
"tuesday": [{"end_time": "17:00:59", "start_time": "10:00:00"}],
|
||||
"wednesday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
},
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"holiday_group": {
|
||||
"description": "",
|
||||
"holidays": [
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-26 00:00:00Z",
|
||||
"id": "d51777c4-9559-45aa-8e23-434995d9d2a1",
|
||||
"is_template": False,
|
||||
"name": "Holiday Name 1",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-25 00:00:00Z",
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"end_time": "2023-08-27 00:00:00Z",
|
||||
"id": "d23a4226-765f-4967-b84f-6dfd53f33c89",
|
||||
"is_template": False,
|
||||
"name": "Holiday Name 2",
|
||||
"repeat": False,
|
||||
"start_time": "2023-08-26 00:00:00Z",
|
||||
},
|
||||
],
|
||||
"id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"is_default": False,
|
||||
"name": "Holiday Group-16928679155714",
|
||||
"template_name": "",
|
||||
},
|
||||
"holiday_schedule": [
|
||||
{"end_time": "11:45:59", "start_time": "09:15:00"}
|
||||
],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_schedule(schedule_id=schedule_id).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_schedules(self) -> None:
|
||||
"""5.17 Fetch All Schedules"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/schedules",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "73facd6c-839e-4521-a4f4-c07e1d44e748",
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"is_default": True,
|
||||
"name": "Always Access",
|
||||
"status": 1,
|
||||
"type": "access",
|
||||
},
|
||||
{
|
||||
"id": "58c0f89b-f35c-4d2c-af7b-8b8918df2c31",
|
||||
"holiday_group_id": "75660081-431b-4dbe-9b98-e0257877118e",
|
||||
"is_default": False,
|
||||
"name": "UNVR Schedule",
|
||||
"status": 1,
|
||||
"type": "access",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_schedules().success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_delete_schedule(self) -> None:
|
||||
"""5.18 Delete Schedule"""
|
||||
schedule_id = ScheduleId("1d31b648-b8ff-4bd1-b742-60dbd70592cd")
|
||||
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/access_policies/schedules/{schedule_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "msg": "success", "data": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_schedule(schedule_id=schedule_id).success_or_raise()
|
189
tests/test_credential.py
Normal file
189
tests/test_credential.py
Normal file
@ -0,0 +1,189 @@
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import DeviceId, NfcCardEnrollmentSessionId, NfcCardToken
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class CredentialTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_generate_pin_code(self) -> None:
|
||||
"""6.1 Generate PIN Code"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/credentials/pin_codes",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "data": "67203419", "msg": "success"},
|
||||
)
|
||||
resp = self.client.generate_pin_code().success_or_raise()
|
||||
assert resp.data == "67203419"
|
||||
|
||||
@responses.activate
|
||||
def test_begin_enroll_card(self) -> None:
|
||||
"""6.2 Enroll NFC Card"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/sessions",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{"device_id": "0418d6a2bb7a", "reset_ua_card": False}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"msg": "success",
|
||||
"data": {"session_id": "e8a97c52-6676-4c48-8589-bd518afc4094"},
|
||||
},
|
||||
)
|
||||
resp = self.client.begin_enroll_card(
|
||||
device_id=DeviceId("0418d6a2bb7a"), reset_ua_card=False
|
||||
).success_or_raise()
|
||||
assert resp.data.session_id == "e8a97c52-6676-4c48-8589-bd518afc4094"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_enroll_card_status(self) -> None:
|
||||
"""6.3 Fetch NFC Card Enrollment Status"""
|
||||
session_id = NfcCardEnrollmentSessionId("e8a97c52-6676-4c48-8589-bd518afc4094")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/sessions/{session_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"msg": "success",
|
||||
"data": {
|
||||
# NOTE: altered from API docs to match testing on real instance (was "card_id")
|
||||
"id": "014A3151",
|
||||
"token": "821f90b262e90c5c0fbcddf3d6d2f3b94cc015d6e8104ab4fb96e4c8b8e90cb7",
|
||||
},
|
||||
},
|
||||
)
|
||||
resp = self.client.fetch_enroll_card_status(
|
||||
session_id=session_id
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "014A3151"
|
||||
assert (
|
||||
resp.data.token
|
||||
== "821f90b262e90c5c0fbcddf3d6d2f3b94cc015d6e8104ab4fb96e4c8b8e90cb7"
|
||||
)
|
||||
|
||||
@responses.activate
|
||||
def test_remove_enrollment_session(self) -> None:
|
||||
"""6.4 Remove a Session Created for NFC Card Enrollment"""
|
||||
session_id = NfcCardEnrollmentSessionId("e8a97c52-6676-4c48-8589-bd518afc4094")
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/sessions/{session_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success", "data": "success"},
|
||||
)
|
||||
resp = self.client.remove_enrollment_session(
|
||||
session_id=session_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_nfc_card(self) -> None:
|
||||
"""6.7 Fetch NFC Card"""
|
||||
nfc_card_token = NfcCardToken(
|
||||
"f77d69b08eaf5eb5d647ac1a0a73580f1b27494b345f40f54fa022a8741fa15c"
|
||||
)
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/tokens/{nfc_card_token}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"alias": "",
|
||||
"card_type": "ua_card",
|
||||
"display_id": "100005",
|
||||
"note": "100005",
|
||||
"status": "assigned",
|
||||
"token": "f77d69b08eaf5eb5d647ac1a0a73580f1b27494b345f40f54fa022a8741fa15c",
|
||||
"user": {
|
||||
"avatar": "",
|
||||
"first_name": "H",
|
||||
"id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"last_name": "L",
|
||||
"name": "H L",
|
||||
},
|
||||
"user_id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"user_type": "USER",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_nfc_card(nfc_card_token).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_nfc_cards(self) -> None:
|
||||
"""6.8 Fetch All NFC Cards"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/tokens",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"page_num": 1, "page_size": 25}),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"alias": "",
|
||||
"card_type": "ua_card",
|
||||
"display_id": "100004",
|
||||
"note": "100004",
|
||||
"status": "assigned",
|
||||
"token": "9e24cdfafebf63e58fd02c5f67732b478948e5793d31124239597d9a86b30dc4",
|
||||
"user": {
|
||||
"avatar": "",
|
||||
"first_name": "H",
|
||||
"id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"last_name": "L",
|
||||
"name": "H L",
|
||||
},
|
||||
"user_id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"user_type": "USER",
|
||||
},
|
||||
{
|
||||
"alias": "F77D69B03",
|
||||
"card_type": "ua_card",
|
||||
"display_id": "100005",
|
||||
"note": "100005",
|
||||
"status": "assigned",
|
||||
"token": "f77d69b08eaf5eb5d647ac1a0a73580f1b27494b345f40f54fa022a8741fa15c",
|
||||
"user": {
|
||||
"avatar": "",
|
||||
"first_name": "H2",
|
||||
"id": "34dc90a7-409f-4bf8-a5a8-1c59535a21b9",
|
||||
"last_name": "L",
|
||||
"name": "H2 L",
|
||||
},
|
||||
"user_id": "34dc90a7-409f-4bf8-a5a8-1c59535a21b9",
|
||||
"user_type": "VISITOR",
|
||||
},
|
||||
],
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 2, "total": 2},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_nfc_cards(
|
||||
page_num=1, page_size=25
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_delete_nfc_card(self) -> None:
|
||||
"""6.9 Delete NFC Card"""
|
||||
nfc_card_token = NfcCardToken(
|
||||
"f77d69b08eaf5eb5d647ac1a0a73580f1b27494b345f40f54fa022a8741fa15c"
|
||||
)
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/credentials/nfc_cards/tokens/{nfc_card_token}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "data": "success", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_nfc_card(nfc_card_token).success_or_raise()
|
||||
assert resp.data == "success"
|
42
tests/test_device.py
Normal file
42
tests/test_device.py
Normal file
@ -0,0 +1,42 @@
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class CredentialTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_fetch_devices(self) -> None:
|
||||
"""8.1 Fetch Devices"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/devices",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
# XXX: verify correct response, as per note
|
||||
# NOTE: api docs had this as a list of lists of objects
|
||||
"data": [
|
||||
{
|
||||
"full_name": "UNVR - Main Floor - Door 3855 - UA-HUB-3855",
|
||||
"id": "7483c2773855",
|
||||
"name": "UA-HUB-3855",
|
||||
"type": "UAH",
|
||||
},
|
||||
{
|
||||
"full_name": "UNVR - Main Floor - Door 3855 - out - UA-LITE-8CED",
|
||||
"id": "f492bfd28ced",
|
||||
"name": "UA-LITE-8CED",
|
||||
"type": "UDA-LITE",
|
||||
},
|
||||
{
|
||||
"full_name": "UNVR - Main Floor - Door 3855 - in - UA-G2-PRO-BB7A",
|
||||
"id": "0418d6a2bb7a",
|
||||
"name": "UA-G2-PRO-BB7A",
|
||||
"type": "UA-G2-PRO",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
resp = self.client.fetch_devices().success_or_raise()
|
||||
assert resp.data[0].name == "UA-HUB-3855"
|
434
tests/test_space.py
Normal file
434
tests/test_space.py
Normal file
@ -0,0 +1,434 @@
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import DoorGroupId, DoorId, DoorLockingRuleType
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class SpaceTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_fetch_door_group_topology(self) -> None:
|
||||
"""7.1 Fetch Door Group Topology"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/topology",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"name": "All Doors",
|
||||
"resource_topologies": [
|
||||
{
|
||||
"id": "9bee6e0e-108d-4c52-9107-76f2c7dea4f1",
|
||||
"name": "Main Floor",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"name": "Door 3855",
|
||||
"type": "door",
|
||||
"is_bind_hub": True,
|
||||
}
|
||||
],
|
||||
"type": "floor",
|
||||
}
|
||||
],
|
||||
"type": "building",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"name": "customized group",
|
||||
"resource_topologies": [
|
||||
{
|
||||
"id": "9bee6e0e-108d-4c52-9107-76f2c7dea4f1",
|
||||
"name": "Main Floor",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"name": "Door 3855",
|
||||
"type": "door",
|
||||
"is_bind_hub": True,
|
||||
}
|
||||
],
|
||||
"type": "floor",
|
||||
}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door_group_topology().success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_create_door_group(self) -> None:
|
||||
"""7.2 Create Door Group"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/door_groups",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"group_name": "Test",
|
||||
"resources": ["6ff875d2-af87-470b-9cb5-774c6596afc8"],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "0140fa3d-8973-4305-a0ce-5306ae277878",
|
||||
"name": "Customized Door Group",
|
||||
"resources": [
|
||||
{"id": "6ff875d2-af87-470b-9cb5-774c6596afc8", "type": "door"}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
# XXX: TODO: test if this supports both doors and door groups
|
||||
resp = self.client.create_door_group(
|
||||
group_name="Test",
|
||||
resources=[DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8")],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_door_group_building(self) -> None:
|
||||
"""7.3 Fetch Door Group"""
|
||||
door_group_id = DoorGroupId("d5573467-d6b3-4e8f-8e48-8a322b91664a")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/{door_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
# Group type is building
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"name": "All Doors",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"name": "Door 3855",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "7cc1823f-9cdb-447b-b01b-4cb2abc661c0",
|
||||
"name": "A2 Door",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "ececa68e-239f-4b82-adc4-0c9ce70c60ff",
|
||||
"name": "A3",
|
||||
"type": "door",
|
||||
},
|
||||
],
|
||||
"type": "building",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door_group(
|
||||
door_group_id=door_group_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_door_group_customized_groups(self) -> None:
|
||||
"""7.3 Fetch Door Group"""
|
||||
door_group_id = DoorGroupId("1be0c995-0347-4cb2-93b3-66a9624af568")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/{door_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
# Customized groups
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "1be0c995-0347-4cb2-93b3-66a9624af568",
|
||||
"name": "Door Group 01",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
"name": "Door 385",
|
||||
}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door_group(
|
||||
door_group_id=door_group_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_update_door_group(self) -> None:
|
||||
"""7.4 Update Door Group"""
|
||||
door_group_id = DoorGroupId("0140fa3d-8973-4305-a0ce-5306ae277878")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/{door_group_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"resources": [
|
||||
"6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"5a2c3d4e-1f6b-4c8d-9e0f-2a3b4c5d6e7f",
|
||||
"2p3q4r5s-6t7u-8v9w-x0y1-z2a3b4c5d6e",
|
||||
]
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "0140fa3d-8973-4305-a0ce-5306ae277878",
|
||||
"name": "test",
|
||||
"resources": [
|
||||
{"id": "6ff875d2-af87-470b-9cb5-774c6596afc8", "type": "door"}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
# XXX: TODO: verify that this accepts both Doors and Door Groups
|
||||
resp = self.client.update_door_group(
|
||||
door_group_id=door_group_id,
|
||||
resources=[
|
||||
DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8"),
|
||||
DoorId("5a2c3d4e-1f6b-4c8d-9e0f-2a3b4c5d6e7f"),
|
||||
DoorGroupId("2p3q4r5s-6t7u-8v9w-x0y1-z2a3b4c5d6e"),
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_update_door_group_delete_resources(self) -> None:
|
||||
"""7.4 Update Door Group"""
|
||||
door_group_id = DoorGroupId("0140fa3d-8973-4305-a0ce-5306ae277878")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/{door_group_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher({"resources": []}),
|
||||
],
|
||||
# NOTE: no example provided in API docs
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"id": "0140fa3d-8973-4305-a0ce-5306ae277878",
|
||||
"name": "test",
|
||||
"resources": [],
|
||||
"type": "access",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.update_door_group(
|
||||
door_group_id=door_group_id,
|
||||
resources=[],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_door_groups(self) -> None:
|
||||
"""7.5 Fetch All Door Groups"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/door_groups",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"name": "Test",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
{
|
||||
"id": "1907cc46-0a73-4077-94c1-95b625bdb0f8",
|
||||
"name": "Test2",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
}
|
||||
],
|
||||
"type": "access",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_door_groups().success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_delete_door_groups(self) -> None:
|
||||
"""7.6 Delete Door Group"""
|
||||
door_group_id = DoorGroupId("0140fa3d-8973-4305-a0ce-5306ae277878")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/door_groups/{door_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "data": "success", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_door_group(
|
||||
door_group_id=door_group_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_door(self) -> None:
|
||||
"""7.7 Fetch Door"""
|
||||
door_id = DoorId("0ed545f8-2fcd-4839-9021-b39e707f6aa9")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/doors/{door_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"door_lock_relay_status": "lock",
|
||||
"door_position_status": "",
|
||||
"floor_id": "3275af8d-3fa7-4902-a11b-011e41c8464a",
|
||||
"full_name": "UNVR - 1F - Main Door",
|
||||
"id": "0ed545f8-2fcd-4839-9021-b39e707f6aa9",
|
||||
"is_bind_hub": True,
|
||||
"name": "Main Door",
|
||||
"type": "door",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door(door_id=door_id).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_doors(self) -> None:
|
||||
"""7.8 Fetch All Doors"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/doors",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"door_lock_relay_status": "unlock",
|
||||
"door_position_status": "open",
|
||||
"floor_id": "23c5db06-b59b-494d-94f1-23e88fbe4909",
|
||||
"full_name": "UNVR - 2F - A2 Door",
|
||||
"id": "0ed545f8-2fcd-4839-9021-b39e707f6aa9",
|
||||
"is_bind_hub": True,
|
||||
"name": "A2 Door",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"door_lock_relay_status": "lock",
|
||||
"door_position_status": "close",
|
||||
"floor_id": "7c62b4b3-692f-44ea-8eb8-e212833b4e0f",
|
||||
"full_name": "UNVR - 1F - Door 3855",
|
||||
"id": "5785e97b-6123-4596-ba49-b6e51164db9b",
|
||||
"is_bind_hub": True,
|
||||
"name": "Door 3855",
|
||||
"type": "door",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_doors().success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_unlock_door(self) -> None:
|
||||
"""7.9 Remote Door Unlocking"""
|
||||
door_id = DoorId("5785e97b-6123-4596-ba49-b6e51164db9b")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/doors/{door_id}/unlock",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "data": "success", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.unlock_door(door_id=door_id).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_set_temporary_door_locking_rule(self) -> None:
|
||||
"""7.10 Set Temporary Door Locking Rule"""
|
||||
door_id = DoorId("e4978b83-203d-4015-97df-b86efc91cb0c")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/doors/{door_id}/lock_rule",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher({"type": "custom", "interval": 10}),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": "success", "msg": "success"},
|
||||
)
|
||||
|
||||
# Customized 10-minute unlocked
|
||||
resp = self.client.set_temporary_door_locking_rule(
|
||||
door_id=door_id, type_=DoorLockingRuleType.CUSTOM, interval=10
|
||||
).success_or_raise()
|
||||
|
||||
# XXX: TODO: test the other examples?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_door_locking_rule(self) -> None:
|
||||
"""7.11 Fetch Door Locking Rule"""
|
||||
door_id = DoorId("e4978b83-203d-4015-97df-b86efc91cb0c")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/doors/{door_id}/lock_rule",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
# Keep it locked
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {"ended_time": 3602128309, "type": "keep_lock"},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door_locking_rule(door_id=door_id).success_or_raise()
|
||||
# XXX: TODO: test the other examples?
|
||||
|
||||
@responses.activate
|
||||
def test_set_door_emergency_status(self) -> None:
|
||||
"""7.12 Set Door Emergency Status"""
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/doors/settings/emergency",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher({"lockdown": True, "evacuation": False}),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": "success", "msg": "success"},
|
||||
)
|
||||
|
||||
# Keep it locked
|
||||
resp = self.client.set_door_emergency_status(
|
||||
lockdown=True, evacuation=False
|
||||
).success_or_raise()
|
||||
# XXX: TODO: test the other examples?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_door_emergency_status(self) -> None:
|
||||
"""7.13 Fetch Door Emergency Status"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/doors/settings/emergency",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {"evacuation": True, "lockdown": False},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_door_emergency_status().success_or_raise()
|
118
tests/test_system_log.py
Normal file
118
tests/test_system_log.py
Normal file
@ -0,0 +1,118 @@
|
||||
import datetime
|
||||
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import SystemLogTopic, UserId
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class SystemLogTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_fetch_system_logs(self) -> None:
|
||||
"""9.2 Fetch System Logs"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/system/logs",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"page_size": 25, "page_num": 1}),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"topic": "door_openings",
|
||||
"since": 1690770546,
|
||||
"until": 1690771546,
|
||||
"actor_id": "3e1f196e-c97b-4748-aecb-eab5e9c251b2",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"hits": [
|
||||
{
|
||||
"@timestamp": "2023-07-11T12:11:27Z",
|
||||
"_id": "",
|
||||
"_source": {
|
||||
"actor": {
|
||||
"alternate_id": "",
|
||||
"alternate_name": "",
|
||||
"display_name": "N/A",
|
||||
"id": "",
|
||||
"type": "user",
|
||||
},
|
||||
"authentication": {
|
||||
"credential_provider": "NFC",
|
||||
"issuer": "6FC02554",
|
||||
},
|
||||
"event": {
|
||||
"display_message": "Access Denied / Unknown (NFC)",
|
||||
"published": 1689077487000,
|
||||
"reason": "",
|
||||
"result": "BLOCKED",
|
||||
"type": "access.door.unlock",
|
||||
# NOTE: missing from example, but present in live api
|
||||
"log_key": "",
|
||||
},
|
||||
"target": [
|
||||
{
|
||||
"alternate_id": "",
|
||||
"alternate_name": "",
|
||||
"display_name": "UA-HUB-3855",
|
||||
"id": "7483c2773855",
|
||||
"type": "UAH",
|
||||
}
|
||||
],
|
||||
},
|
||||
"tag": "access",
|
||||
}
|
||||
]
|
||||
},
|
||||
# NOTE: example missing `msg` and `pagination`, had `page` and `total` instead
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 10000, "total": 1},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_system_logs(
|
||||
page_num=1,
|
||||
page_size=25,
|
||||
topic=SystemLogTopic.DOOR_OPENINGS,
|
||||
since=datetime.datetime.fromtimestamp(1690770546, tz=datetime.UTC),
|
||||
until=datetime.datetime.fromtimestamp(1690771546, tz=datetime.UTC),
|
||||
actor_id=UserId("3e1f196e-c97b-4748-aecb-eab5e9c251b2"),
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_export_system_logs(self) -> None:
|
||||
"""9.3 Export System Logs"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/system/logs/export",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"topic": "door_openings",
|
||||
"since": 1690770546,
|
||||
"until": 1690771546,
|
||||
"timezone": "America/New_York",
|
||||
"actor_id": "3e1f196e-c97b-4748-aecb-eab5e9c251b2",
|
||||
}
|
||||
),
|
||||
],
|
||||
# NOTE: example not provided by API
|
||||
# TODO: what does the API do on errors?
|
||||
body=b"time,actor.alternate_id,actor.alternate_name,actor.display_name,actor.id,actor.type,authentication.credential_provider,authentication.issuer,event.display_message,event.published,event.reason,event.result,event.type,target1.alternate_id,target1.alternate_name,target1.display_name,target1.id,target1.type,target2.alternate_id,target2.alternate_name,target2.display_name,target2.id,target2.type,target3.alternate_id,target3.alternate_name,target3.display_name,target3.id,target3.type,target4.alternate_id,target4.alternate_name,target4.display_name,target4.id,target4.type,target5.alternate_id,target5.alternate_name,target5.display_name,target5.id,target5.type\n",
|
||||
)
|
||||
|
||||
resp = self.client.export_system_logs(
|
||||
topic=SystemLogTopic.DOOR_OPENINGS,
|
||||
since=datetime.datetime.fromtimestamp(1690770546, tz=datetime.UTC),
|
||||
until=datetime.datetime.fromtimestamp(1690771546, tz=datetime.UTC),
|
||||
timezone="America/New_York",
|
||||
actor_id=UserId("3e1f196e-c97b-4748-aecb-eab5e9c251b2"),
|
||||
)
|
||||
|
||||
# TODO:
|
||||
# - 9.4 Fetch Resources in System Logs
|
||||
# - 9.5 Fetch Static Resources in System Logs
|
249
tests/test_unifi_identity.py
Normal file
249
tests/test_unifi_identity.py
Normal file
@ -0,0 +1,249 @@
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import (
|
||||
IdentityInvitationUser,
|
||||
IdentityResourceType,
|
||||
UserGroupId,
|
||||
UserId,
|
||||
)
|
||||
from unifi_access.schemas._base import IdentityResourceId
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class UnifiIdentityTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_send_unifi_identity_invitations(self) -> None:
|
||||
"""10.1 Send UniFi Identity Invitations"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/users/identity/invitations",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
[
|
||||
{
|
||||
"user_id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"email": "example@*.com",
|
||||
}
|
||||
]
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": [], "msg": "success"},
|
||||
)
|
||||
resp = self.client.send_unifi_identity_invitations(
|
||||
[
|
||||
IdentityInvitationUser(
|
||||
user_id=UserId("e0051e08-c4d5-43db-87c8-a9b19cb66513"),
|
||||
email="example@*.com",
|
||||
)
|
||||
]
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_send_unifi_identity_invitations_email_failure(self) -> None:
|
||||
"""10.1 Send UniFi Identity Invitations"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/users/identity/invitations",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
[
|
||||
{
|
||||
"user_id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
"email": "example@*.com",
|
||||
}
|
||||
]
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"error_code": "",
|
||||
"error_msg": "invalid email",
|
||||
"user_email": "example@*.com",
|
||||
"user_id": "e0051e08-c4d5-43db-87c8-a9b19cb66513",
|
||||
}
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
# XXX: not sure what behavior here should be...
|
||||
|
||||
resp = self.client.send_unifi_identity_invitations(
|
||||
[
|
||||
IdentityInvitationUser(
|
||||
user_id=UserId("e0051e08-c4d5-43db-87c8-a9b19cb66513"),
|
||||
email="example@*.com",
|
||||
)
|
||||
]
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_available_resources(self) -> None:
|
||||
"""10.2 Fetch Available Resources"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/users/identity/assignments",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"resource_type": "ev_station,wifi,vpn"}),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"ev_station": [],
|
||||
"vpn": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a9c188cb71cfac8e9d",
|
||||
"metadata": None,
|
||||
"name": "UDM Pro",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
"wifi": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a8c188cb71cfac8e9a",
|
||||
"metadata": None,
|
||||
"name": "UniFi Identity",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_available_resources(
|
||||
resource_type=[
|
||||
IdentityResourceType.EV_STATION,
|
||||
IdentityResourceType.WIFI,
|
||||
IdentityResourceType.VPN,
|
||||
]
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_assign_resources_to_user(self) -> None:
|
||||
"""10.3 Assign Resources to Users"""
|
||||
user_id = UserId("b602879b-b857-400b-970b-336d4cb881ad")
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/identity/assignments",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"resource_type": "wifi",
|
||||
"resource_ids": ["65dff9a8c188cb71cfac8e9a"],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": None, "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_resources_to_user(
|
||||
user_id=user_id,
|
||||
resource_type=IdentityResourceType.WIFI,
|
||||
resource_ids=[IdentityResourceId("65dff9a8c188cb71cfac8e9a")],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_resources_assigned_to_user(self) -> None:
|
||||
"""10.4 Fetch Resources Assigned to Users"""
|
||||
user_id = UserId("b602879b-b857-400b-970b-336d4cb881ad")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/identity/assignments",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"ev_station": [],
|
||||
"vpn": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a9c188cb71cfac8e9d",
|
||||
"metadata": {"has_ip": True},
|
||||
"name": "UDM Pro",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
"wifi": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a8c188cb71cfac8e9a",
|
||||
"metadata": None,
|
||||
"name": "UniFi Identity",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_resources_assigned_to_user(
|
||||
user_id=user_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_assign_resources_to_user_group(self) -> None:
|
||||
"""10.5 Assign Resources to User Groups"""
|
||||
user_group_id = UserGroupId("7476c839-8e10-472e-894f-c5b8254c35b5")
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/identity/assignments",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"resource_type": "wifi",
|
||||
"resource_ids": ["65dff9a8c188cb71cfac8e9a"],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": None, "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_resources_to_user_group(
|
||||
user_group_id=user_group_id,
|
||||
resource_type=IdentityResourceType.WIFI,
|
||||
resource_ids=[IdentityResourceId("65dff9a8c188cb71cfac8e9a")],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_resources_assigned_to_user_group(self) -> None:
|
||||
"""10.6 Fetch Resources Assigned to User Groups"""
|
||||
user_group_id = UserGroupId("b602879b-b857-400b-970b-336d4cb881ad")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/identity/assignments",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"ev_station": [],
|
||||
"vpn": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a9c188cb71cfac8e9d",
|
||||
"metadata": {"has_ip": True},
|
||||
"name": "UDM Pro",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
"wifi": [
|
||||
{
|
||||
"deleted": False,
|
||||
"id": "65dff9a8c188cb71cfac8e9a",
|
||||
"metadata": None,
|
||||
"name": "UniFi Identity",
|
||||
"short_name": "",
|
||||
}
|
||||
],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_resources_assigned_to_user_group(
|
||||
user_group_id=user_group_id
|
||||
).success_or_raise()
|
720
tests/test_user.py
Normal file
720
tests/test_user.py
Normal file
@ -0,0 +1,720 @@
|
||||
import datetime
|
||||
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import (
|
||||
AccessPolicyId,
|
||||
NfcCardToken,
|
||||
UserGroupId,
|
||||
UserId,
|
||||
UserStatus,
|
||||
)
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class UserTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_register_user(self) -> None:
|
||||
"""3.2 User Registration"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/users",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"employee_number": "100000",
|
||||
"onboard_time": 1689150139,
|
||||
"user_email": "example@*.com",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "37f2b996-c2c5-487b-aa22-8b453ff14a4b",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"employee_number": "100000",
|
||||
"onboard_time": 1689150139,
|
||||
"user_email": "example@*.com",
|
||||
# NOTE: following fields missing from example, but present in real responses
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"email": "",
|
||||
"email_status": "UNVERIFIED",
|
||||
"full_name": "First Name Last Name",
|
||||
"phone": "",
|
||||
"status": "ACTIVE",
|
||||
"username": "",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.register_user(
|
||||
first_name="First Name",
|
||||
last_name="Last Name",
|
||||
employee_number="100000",
|
||||
onboard_time=datetime.datetime.fromtimestamp(1689150139, tz=datetime.UTC),
|
||||
user_email="example@*.com",
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "37f2b996-c2c5-487b-aa22-8b453ff14a4b"
|
||||
|
||||
@responses.activate
|
||||
def test_update_user(self) -> None:
|
||||
"""3.3 Update User"""
|
||||
user_id = UserId("37f2b996-c2c5-487b-aa22-8b453ff14a4b")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"first_name": "H",
|
||||
"last_name": "L",
|
||||
"employee_number": "",
|
||||
"user_email": "example@*.com",
|
||||
# XXX: TODO: this is not listed as a valid param in their docs...
|
||||
# "pin_code": "",
|
||||
"onboard_time": 1689150139,
|
||||
"status": "ACTIVE",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success", "data": None},
|
||||
)
|
||||
|
||||
resp = self.client.update_user(
|
||||
user_id=user_id,
|
||||
first_name="H",
|
||||
last_name="L",
|
||||
employee_number="",
|
||||
onboard_time=datetime.datetime.fromtimestamp(1689150139, tz=datetime.UTC),
|
||||
user_email="example@*.com",
|
||||
status=UserStatus.ACTIVE,
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_user(self) -> None:
|
||||
"""3.4 Fetch User"""
|
||||
user_id = UserId("37f2b996-c2c5-487b-aa22-8b453ff14a4b")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"expand[]": "access_policy"}),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"access_policies": [
|
||||
{
|
||||
"id": "edbc80df-3698-49fd-8b53-f1867f104947",
|
||||
"name": "test",
|
||||
"resources": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
],
|
||||
"schedule_id": "73facd6c-839e-4521-a4f4-c07e1d44e748",
|
||||
}
|
||||
],
|
||||
"access_policy_ids": ["edbc80df-3698-49fd-8b53-f1867f104947"],
|
||||
"employee_number": "",
|
||||
"first_name": "***",
|
||||
"id": "37f2b996-c2c5-487b-aa22-8b453ff14a4b",
|
||||
"last_name": "L",
|
||||
"user_email": "example@*.com",
|
||||
"nfc_cards": [
|
||||
{
|
||||
"id": "100001",
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0",
|
||||
"type": "ua_card",
|
||||
}
|
||||
],
|
||||
"onboard_time": 1689047588,
|
||||
"pin_code": {
|
||||
"token": "5f742ee4424e5a7dd265de3461009b9ebafa1fb9d6b15018842055cc0466ac56"
|
||||
},
|
||||
"status": "ACTIVE",
|
||||
# NOTE: following fields missing from example, but present in real responses
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"email": "",
|
||||
"email_status": "UNVERIFIED",
|
||||
"full_name": "*** L",
|
||||
"phone": "",
|
||||
"username": "",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_user(
|
||||
user_id=user_id, expand_access_policies=True
|
||||
).success_or_raise()
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_users(self) -> None:
|
||||
"""3.5 Fetch All Users"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/users",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher(
|
||||
{"expand[]": "access_policy", "page_num": 1, "page_size": 25}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"access_policies": [
|
||||
{
|
||||
"id": "73f15cab-c725-4a76-a419-a4026d131e96",
|
||||
"name": "Default Admin Policy",
|
||||
"resources": [
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule_id": "73facd6c-839e-4521-a4f4-c07e1d44e748",
|
||||
}
|
||||
],
|
||||
"access_policy_ids": ["73f15cab-c725-4a76-a419-a4026d131e96"],
|
||||
"employee_number": "",
|
||||
"first_name": "UniFi",
|
||||
"id": "83569f9b-0096-48ab-b2e4-5c9a598568a8",
|
||||
"last_name": "User",
|
||||
"user_email": "",
|
||||
"nfc_cards": [],
|
||||
"onboard_time": 0,
|
||||
"pin_code": None,
|
||||
"status": "ACTIVE",
|
||||
# NOTE: following fields missing from example, but present in real responses
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"email": "",
|
||||
"email_status": "UNVERIFIED",
|
||||
"full_name": "UniFi User",
|
||||
"phone": "",
|
||||
"username": "",
|
||||
},
|
||||
{
|
||||
"access_policies": [
|
||||
{
|
||||
"id": "c1682fb8-ef6e-4fe2-aa8a-b6f29df753ff",
|
||||
"name": "policy_1690272668035",
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
}
|
||||
],
|
||||
"schedule_id": "0616ef06-b807-4372-9ae0-7a87e12e4019",
|
||||
}
|
||||
],
|
||||
"access_policy_ids": ["c1682fb8-ef6e-4fe2-aa8a-b6f29df753ff"],
|
||||
"employee_number": "",
|
||||
"first_name": "Ttttt",
|
||||
"id": "3a3ba57a-796e-46e0-b8f3-478bb70a114d",
|
||||
"last_name": "Tttt",
|
||||
"nfc_cards": [],
|
||||
"onboard_time": 1689048000,
|
||||
"pin_code": None,
|
||||
"status": "ACTIVE",
|
||||
# NOTE: following fields missing from example, but present in real responses
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"user_email": "",
|
||||
"email": "",
|
||||
"email_status": "UNVERIFIED",
|
||||
"full_name": "Ttttt Tttt",
|
||||
"phone": "",
|
||||
"username": "",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
"pagination": {"page_num": 1, "page_size": 97, "total": 97},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_users(
|
||||
expand_access_policies=True, page_num=1, page_size=25
|
||||
).success_or_raise()
|
||||
assert resp.pagination
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_assign_access_policy_to_user(self) -> None:
|
||||
"""3.6 Assign Access Policy to User"""
|
||||
user_id = UserId("37f2b996-c2c5-487b-aa22-8b453ff14a4b")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/access_policies",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"access_policy_ids": [
|
||||
"03895c7f-9f53-4334-812b-5db9c122c109",
|
||||
"3b6bcb0c-7498-44cf-8615-00a96d824cbe",
|
||||
]
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
resp = self.client.assign_access_policy_to_user(
|
||||
user_id=user_id,
|
||||
access_policy_ids=[
|
||||
AccessPolicyId("03895c7f-9f53-4334-812b-5db9c122c109"),
|
||||
AccessPolicyId("3b6bcb0c-7498-44cf-8615-00a96d824cbe"),
|
||||
],
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_assign_nfc_card_to_user(self) -> None:
|
||||
"""3.7 Assign NFC Card to User"""
|
||||
user_id = UserId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/nfc_cards",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0",
|
||||
"force_add": True,
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
resp = self.client.assign_nfc_card_to_user(
|
||||
user_id=user_id,
|
||||
token=NfcCardToken(
|
||||
"d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0"
|
||||
),
|
||||
force_add=True,
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_unassign_nfc_card_from_user(self) -> None:
|
||||
"""3.8 Unassign NFC Card from User"""
|
||||
user_id = UserId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/nfc_cards/delete",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
resp = self.client.unassign_nfc_card_from_user(
|
||||
user_id=user_id,
|
||||
token=NfcCardToken(
|
||||
"d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0"
|
||||
),
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_assign_pin_code_to_user(self) -> None:
|
||||
"""3.9 Assign PIN Code to User"""
|
||||
user_id = UserId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/pin_codes",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher({"pin_code": "57301208"}),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
resp = self.client.assign_pin_code_to_user(
|
||||
user_id=user_id, pin_code="57301208"
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_unassign_pin_code_from_user(self) -> None:
|
||||
"""3.10 Unassign PIN Code from User"""
|
||||
user_id = UserId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/pin_codes",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
resp = self.client.unassign_pin_code_from_user(
|
||||
user_id=user_id,
|
||||
).success_or_raise()
|
||||
assert resp.data is None
|
||||
|
||||
@responses.activate
|
||||
def test_create_user_group(self) -> None:
|
||||
"""3.11 Create User Group"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/user_groups",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "Group Name",
|
||||
"up_id": "013d05d3-7262-4908-ba69-badbbbf8f5a6",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
# NOTE: completely missing from docs
|
||||
"data": {
|
||||
"full_name": "UniFi-CloudKey-Gen2-Plus / Group Name",
|
||||
"id": "7a681dd8-4d38-4bbf-a061-2fd5d71db5c1",
|
||||
"name": "Group Name",
|
||||
"up_id": "bdec1374-9547-4541-9761-d9e66cb1c367",
|
||||
"up_ids": ["bdec1374-9547-4541-9761-d9e66cb1c367"],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.create_user_group(
|
||||
name="Group Name", up_id=UserGroupId("013d05d3-7262-4908-ba69-badbbbf8f5a6")
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_user_groups(self) -> None:
|
||||
"""3.12 Fetch All User Groups"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"full_name": "Group Name",
|
||||
"id": "75011ee6-b7ab-4927-9d9f-dd08ef0a3199",
|
||||
"name": "Group Name",
|
||||
"up_id": "a27899fc-a2d1-4797-8d4d-86118f8555f3",
|
||||
"up_ids": ["a27899fc-a2d1-4797-8d4d-86118f8555f3"],
|
||||
}
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_user_groups().success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_user_group(self) -> None:
|
||||
"""3.13 Fetch User Group"""
|
||||
user_group_id = UserGroupId("75011ee6-b7ab-4927-9d9f-dd08ef0a3199")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"full_name": "Group Name",
|
||||
"id": "75011ee6-b7ab-4927-9d9f-dd08ef0a3199",
|
||||
"name": "Group Name",
|
||||
"up_id": "a27899fc-a2d1-4797-8d4d-86118f8555f3",
|
||||
"up_ids": ["a27899fc-a2d1-4797-8d4d-86118f8555f3"],
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_user_group(
|
||||
user_group_id=user_group_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_update_user_group(self) -> None:
|
||||
"""3.14 Update User Group"""
|
||||
user_group_id = UserGroupId("75011ee6-b7ab-4927-9d9f-dd08ef0a3199")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"name": "Group Name",
|
||||
"up_id": "013d05d3-7262-4908-ba69-badbbbf8f5a6",
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.update_user_group(
|
||||
user_group_id=user_group_id,
|
||||
name="Group Name",
|
||||
up_id=UserGroupId("013d05d3-7262-4908-ba69-badbbbf8f5a6"),
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_delete_user_group(self) -> None:
|
||||
"""3.15 Delete User Group"""
|
||||
user_group_id = UserGroupId("75011ee6-b7ab-4927-9d9f-dd08ef0a3199")
|
||||
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_user_group(
|
||||
user_group_id=user_group_id
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_assign_user_to_user_group(self) -> None:
|
||||
"""3.16 Assign User to User Group"""
|
||||
user_group_id = UserGroupId("75011ee6-b7ab-4927-9d9f-dd08ef0a3199")
|
||||
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/users",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
[
|
||||
"7c6e9102-acb7-4b89-8ed4-7561e6fb706c",
|
||||
"fd63bc4c-52e0-4dbf-a699-e1233339c73b",
|
||||
]
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_user_to_user_group(
|
||||
user_group_id=user_group_id,
|
||||
users=[
|
||||
UserId("7c6e9102-acb7-4b89-8ed4-7561e6fb706c"),
|
||||
UserId("fd63bc4c-52e0-4dbf-a699-e1233339c73b"),
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_unassign_user_from_user_group(self) -> None:
|
||||
"""3.17 Unassign User from User Group"""
|
||||
user_group_id = UserGroupId("75011ee6-b7ab-4927-9d9f-dd08ef0a3199")
|
||||
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/users/delete",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
[
|
||||
"7c6e9102-acb7-4b89-8ed4-7561e6fb706c",
|
||||
"fd63bc4c-52e0-4dbf-a699-e1233339c73b",
|
||||
]
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.unassign_user_from_user_group(
|
||||
user_group_id=user_group_id,
|
||||
users=[
|
||||
UserId("7c6e9102-acb7-4b89-8ed4-7561e6fb706c"),
|
||||
UserId("fd63bc4c-52e0-4dbf-a699-e1233339c73b"),
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_users_in_a_user_group(self) -> None:
|
||||
"""3.18 Fetch Users in a User Group"""
|
||||
user_group_id = UserGroupId("23676a54-382e-4121-aa80-878d2d9bacaa")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/users",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"email": "*@*.com",
|
||||
"email_status": "UNVERIFIED",
|
||||
"employee_number": "1000000",
|
||||
"first_name": "",
|
||||
"full_name": "",
|
||||
"id": "27aa91ac-2924-43d4-82e1-24b6a570d29e",
|
||||
"last_name": "Chen",
|
||||
"onboard_time": 1689150139,
|
||||
"phone": "",
|
||||
"status": "ACTIVE",
|
||||
"user_email": "",
|
||||
"username": "",
|
||||
}
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_users_in_a_user_group(
|
||||
user_group_id=user_group_id,
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_users_in_a_user_group(self) -> None:
|
||||
"""3.19 Fetch All Users in a User Group"""
|
||||
user_group_id = UserGroupId("23676a54-382e-4121-aa80-878d2d9bacaa")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/users/all",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"alias": "",
|
||||
"avatar_relative_path": "",
|
||||
"email": "*@*.com",
|
||||
"email_status": "UNVERIFIED",
|
||||
"employee_number": "1000000",
|
||||
"first_name": "",
|
||||
"full_name": "",
|
||||
"id": "27aa91ac-2924-43d4-82e1-24b6a570d29e",
|
||||
"last_name": "Chen",
|
||||
"onboard_time": 1689150139,
|
||||
"phone": "",
|
||||
"status": "ACTIVE",
|
||||
"user_email": "",
|
||||
"username": "",
|
||||
}
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_users_in_a_user_group(
|
||||
user_group_id=user_group_id,
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_the_access_policies_assigned_to_a_user(self) -> None:
|
||||
"""3.20 Fetch the Access Policies Assigned to a User"""
|
||||
user_id = UserId("27aa91ac-2924-43d4-82e1-24b6a570d29e")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/users/{user_id}/access_policies",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"only_user_policies": "false"}),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "89a4ca95-1502-4ae7-954f-d986b67afe5c",
|
||||
"name": "Default Site Policy",
|
||||
"resources": [
|
||||
{
|
||||
"id": "fd2a06e2-81af-4cf4-9bd5-8bceb5e7b7d7",
|
||||
"type": "door_group",
|
||||
}
|
||||
],
|
||||
"schedule_id": "6b79d12a-2a6e-4463-949c-f1a98fff40d2",
|
||||
},
|
||||
{
|
||||
"id": "bbe48a65-2ac1-4bf6-bd65-bc8f9ee7fb75",
|
||||
"name": "Access Policy Name",
|
||||
"resources": [],
|
||||
"schedule_id": "f7414bcd-f0cc-4d3e-811a-b5ac75f7ddb8",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_the_access_policies_assigned_to_a_user(
|
||||
user_id=user_id, only_user_policies=False
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_assign_access_policy_to_user_group(self) -> None:
|
||||
"""3.21 Assign Access Policy to User Group"""
|
||||
user_group_id = UserGroupId("23676a54-382e-4121-aa80-878d2d9bacaa")
|
||||
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/access_policies",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{"access_policy_ids": ["bbe48a65-2ac1-4bf6-bd65-bc8f9ee7fb75"]}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": None, "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_access_policy_to_user_group(
|
||||
user_group_id=user_group_id,
|
||||
access_policy_ids=[AccessPolicyId("bbe48a65-2ac1-4bf6-bd65-bc8f9ee7fb75")],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_the_access_policies_assigned_to_a_user_group(self) -> None:
|
||||
"""3.22 Fetch the Access Policies Assigned to a User Group"""
|
||||
user_group_id = UserGroupId("23676a54-382e-4121-aa80-878d2d9bacaa")
|
||||
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/user_groups/{user_group_id}/access_policies",
|
||||
match=[matchers.header_matcher(self.common_headers)],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"id": "89a4ca95-1502-4ae7-954f-d986b67afe5c",
|
||||
"name": "Default Site Policy",
|
||||
"resources": [
|
||||
{
|
||||
"id": "fd2a06e2-81af-4cf4-9bd5-8bceb5e7b7d7",
|
||||
"type": "door_group",
|
||||
}
|
||||
],
|
||||
"schedule_id": "6b79d12a-2a6e-4463-949c-f1a98fff40d2",
|
||||
},
|
||||
{
|
||||
"id": "bbe48a65-2ac1-4bf6-bd65-bc8f9ee7fb75",
|
||||
"name": "Access Policy Name",
|
||||
"resources": [],
|
||||
"schedule_id": "f7414bcd-f0cc-4d3e-811a-b5ac75f7ddb8",
|
||||
},
|
||||
],
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_the_access_policies_assigned_to_a_user_group(
|
||||
user_group_id=user_group_id,
|
||||
).success_or_raise()
|
724
tests/test_visitor.py
Normal file
724
tests/test_visitor.py
Normal file
@ -0,0 +1,724 @@
|
||||
import datetime
|
||||
|
||||
import responses
|
||||
from responses import matchers
|
||||
|
||||
from unifi_access.schemas import (
|
||||
DoorGroupId,
|
||||
DoorGroupResource,
|
||||
DoorId,
|
||||
DoorResource,
|
||||
FetchAllVisitorsExpansion,
|
||||
NfcCardToken,
|
||||
TimePeriod,
|
||||
VisitorId,
|
||||
VisitReason,
|
||||
WeekSchedule,
|
||||
)
|
||||
|
||||
from .base import UnifiAccessTests
|
||||
|
||||
|
||||
class VisitorTests(UnifiAccessTests):
|
||||
@responses.activate
|
||||
def test_create_visitor(self) -> None:
|
||||
"""4.2 Create Visitor"""
|
||||
responses.post(
|
||||
f"https://{self.host}/api/v1/developer/visitors",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"first_name": "H",
|
||||
"last_name": "L",
|
||||
"remarks": "",
|
||||
"mobile_phone": "",
|
||||
"email": "",
|
||||
"visitor_company": "",
|
||||
"start_time": 1688546460,
|
||||
"end_time": 1788572799,
|
||||
# NOTE: typo'ed in api docs to "Interviemw"
|
||||
"visit_reason": "Interview",
|
||||
"week_schedule": {
|
||||
"sunday": [],
|
||||
"monday": [],
|
||||
"tuesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"wednesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"thursday": [
|
||||
{"start_time": "11:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"friday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"saturday": [],
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"first_name": "H",
|
||||
"id": "fbe8d920-47d3-4cfd-bda7-bf4b0e26f73c",
|
||||
"last_name": "L",
|
||||
"nfc_cards": [],
|
||||
"resources": [
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"name": "Test Group",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"name": "UNVR",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "369215b0-cabe-49b6-aeaa-e0b7ec6424d5",
|
||||
"name": "visitor-1691671529285",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule": {
|
||||
"id": "1fb849bb-e7e5-4516-8dd9-b78094a6708a",
|
||||
"is_default": False,
|
||||
"name": "schedule-1691671529237",
|
||||
"type": "access",
|
||||
"weekly": {
|
||||
"friday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
"monday": [],
|
||||
"saturday": [],
|
||||
"sunday": [],
|
||||
"thursday": [
|
||||
{"end_time": "17:00:59", "start_time": "11:00:00"}
|
||||
],
|
||||
"tuesday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
"wednesday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
},
|
||||
},
|
||||
"schedule_id": "1fb849bb-e7e5-4516-8dd9-b78094a6708a",
|
||||
"status": "ACTIVE",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.create_visitor(
|
||||
first_name="H",
|
||||
last_name="L",
|
||||
remarks="",
|
||||
mobile_phone="",
|
||||
email="",
|
||||
visitor_company="",
|
||||
start_time=datetime.datetime.fromtimestamp(1688546460, tz=datetime.UTC),
|
||||
end_time=datetime.datetime.fromtimestamp(1788572799, tz=datetime.UTC),
|
||||
visit_reason=VisitReason.INTERVIEW,
|
||||
week_schedule=WeekSchedule(
|
||||
sunday=[],
|
||||
monday=[],
|
||||
tuesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
wednesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
thursday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(11, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
friday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
saturday=[],
|
||||
),
|
||||
resources=[
|
||||
DoorResource(
|
||||
id=DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("5c496423-6d25-4e4f-8cdf-95ad5135188a"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("d5573467-d6b3-4e8f-8e48-8a322b91664a")
|
||||
),
|
||||
],
|
||||
).success_or_raise()
|
||||
assert resp.data.id == "fbe8d920-47d3-4cfd-bda7-bf4b0e26f73c"
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_visitor(self) -> None:
|
||||
"""4.3 Fetch Visitor"""
|
||||
visitor_id = VisitorId("76794bd8-98c0-49b6-9230-ba8c5812cf29")
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"first_name": "Hong+243",
|
||||
"id": "3566867c-fa04-4752-98f6-43cf9a342d4a",
|
||||
"last_name": "Lu",
|
||||
"nfc_cards": [
|
||||
{
|
||||
"id": "100001",
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0",
|
||||
"type": "ua_card",
|
||||
}
|
||||
],
|
||||
"pin_code": {
|
||||
"token": "bc3e3135013e2dcae119390b7897166e8cec3bcf5becb6b05578ab67634559ed"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"id": "fd293ecb-98d2-425b-a020-cb9365ea48b3",
|
||||
"name": "visitor-1690337152955",
|
||||
"type": "door_group",
|
||||
}
|
||||
],
|
||||
"schedule": {
|
||||
"id": "6ccf9e1e-b174-476d-b2fe-96817c780fbf",
|
||||
"is_default": False,
|
||||
"name": "visitor-1690337152955",
|
||||
"type": "access",
|
||||
"weekly": None,
|
||||
},
|
||||
"schedule_id": "6ccf9e1e-b174-476d-b2fe-96817c780fbf",
|
||||
"status": "VISITED",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_visitor(visitor_id=visitor_id).success_or_raise()
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_visitors(self) -> None:
|
||||
"""4.4 Fetch All Visitors"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/visitors",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"page_num": 1, "page_size": 25}),
|
||||
],
|
||||
# NOTE: no example data provided in api docs, so this was
|
||||
# retrieved from a running instance
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"end_time": 1731880901,
|
||||
"first_name": "Test",
|
||||
"id": "faaffd2e-b555-4991-810f-c18b36407c55",
|
||||
"inviter_id": "",
|
||||
"inviter_name": "",
|
||||
"last_name": "Visitor",
|
||||
"location_id": "",
|
||||
"mobile_phone": "",
|
||||
"nfc_cards": [],
|
||||
"remarks": "",
|
||||
"resources": [],
|
||||
"schedule": {
|
||||
"holiday_group": None,
|
||||
"holiday_group_id": "",
|
||||
"holiday_schedule": [],
|
||||
"id": "",
|
||||
"is_default": False,
|
||||
"name": "",
|
||||
"type": "",
|
||||
"weekly": None,
|
||||
},
|
||||
"schedule_id": "",
|
||||
"start_time": 1731794501,
|
||||
"status": "UPCOMING",
|
||||
"visit_reason": "Business",
|
||||
"visitor_company": "",
|
||||
}
|
||||
],
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 1, "total": 1},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_visitors(
|
||||
page_num=1, page_size=25
|
||||
).success_or_raise()
|
||||
assert resp.pagination
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_visitors_by_keyword(self) -> None:
|
||||
"""4.4 Fetch All Visitors"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/visitors",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"keyword": "H"}),
|
||||
],
|
||||
# NOTE: no example data provided in api docs, so this was
|
||||
# retrieved from a running instance
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"end_time": 1731880901,
|
||||
"first_name": "Test H",
|
||||
"id": "faaffd2e-b555-4991-810f-c18b36407c55",
|
||||
"inviter_id": "",
|
||||
"inviter_name": "",
|
||||
"last_name": "Visitor",
|
||||
"location_id": "",
|
||||
"mobile_phone": "",
|
||||
"nfc_cards": [],
|
||||
"remarks": "",
|
||||
"resources": [],
|
||||
"schedule": {
|
||||
"holiday_group": None,
|
||||
"holiday_group_id": "",
|
||||
"holiday_schedule": [],
|
||||
"id": "",
|
||||
"is_default": False,
|
||||
"name": "",
|
||||
"type": "",
|
||||
"weekly": None,
|
||||
},
|
||||
"schedule_id": "",
|
||||
"start_time": 1731794501,
|
||||
"status": "UPCOMING",
|
||||
"visit_reason": "Business",
|
||||
"visitor_company": "",
|
||||
}
|
||||
],
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 1, "total": 1},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_visitors(keyword="H").success_or_raise()
|
||||
assert resp.pagination
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_fetch_all_visitors_with_expand(self) -> None:
|
||||
"""4.4 Fetch All Visitors"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/visitors",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher(
|
||||
{
|
||||
"expand[]": [
|
||||
"access_policy",
|
||||
"resource",
|
||||
"schedule",
|
||||
"nfc_card",
|
||||
"pin_code",
|
||||
]
|
||||
}
|
||||
),
|
||||
],
|
||||
# NOTE: no example data provided in api docs, so this was
|
||||
# retrieved from a running instance
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"end_time": 1731880901,
|
||||
"first_name": "Test",
|
||||
"id": "faaffd2e-b555-4991-810f-c18b36407c55",
|
||||
"inviter_id": "",
|
||||
"inviter_name": "",
|
||||
"last_name": "Visitor",
|
||||
"location_id": "",
|
||||
"mobile_phone": "",
|
||||
"nfc_cards": [],
|
||||
"remarks": "",
|
||||
"resources": [],
|
||||
"schedule": {
|
||||
"holiday_group": None,
|
||||
"holiday_group_id": "",
|
||||
"holiday_schedule": [],
|
||||
"id": "",
|
||||
"is_default": False,
|
||||
"name": "",
|
||||
"type": "",
|
||||
"weekly": None,
|
||||
},
|
||||
"schedule_id": "",
|
||||
"start_time": 1731794501,
|
||||
"status": "UPCOMING",
|
||||
"visit_reason": "Business",
|
||||
"visitor_company": "",
|
||||
}
|
||||
],
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 1, "total": 1},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_visitors(
|
||||
expand=[
|
||||
FetchAllVisitorsExpansion.ACCESS_POLICY,
|
||||
FetchAllVisitorsExpansion.RESOURCE,
|
||||
FetchAllVisitorsExpansion.SCHEDULE,
|
||||
FetchAllVisitorsExpansion.NFC_CARD,
|
||||
FetchAllVisitorsExpansion.PIN_CODE,
|
||||
]
|
||||
).success_or_raise()
|
||||
assert resp.pagination
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
# NOTE: not taken from API example
|
||||
@responses.activate
|
||||
def test_fetch_all_visitors_without_expand(self) -> None:
|
||||
"""4.4 Fetch All Visitors"""
|
||||
responses.get(
|
||||
f"https://{self.host}/api/v1/developer/visitors",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"expand[]": "none"}),
|
||||
],
|
||||
# NOTE: retrieved from a running instance
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": [
|
||||
{
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"end_time": 1731907089,
|
||||
"first_name": "Test",
|
||||
"id": "173c4cb9-e174-4a83-89fa-01ba8f25362f",
|
||||
"inviter_id": "",
|
||||
"inviter_name": "",
|
||||
"last_name": "Visitor",
|
||||
"location_id": "",
|
||||
"mobile_phone": "",
|
||||
"nfc_cards": [],
|
||||
"pin_code": {},
|
||||
"remarks": "",
|
||||
"resources": [],
|
||||
"schedule_id": "",
|
||||
"start_time": 1731820689,
|
||||
"status": "UPCOMING",
|
||||
"visit_reason": "Business",
|
||||
"visitor_company": "",
|
||||
},
|
||||
],
|
||||
"msg": "succ",
|
||||
"pagination": {"page_num": 1, "page_size": 1, "total": 1},
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.fetch_all_visitors(
|
||||
expand=[FetchAllVisitorsExpansion.NONE]
|
||||
).success_or_raise()
|
||||
assert resp.pagination
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_update_visitor(self) -> None:
|
||||
"""4.5 Update Visitor"""
|
||||
visitor_id = VisitorId("37f2b996-c2c5-487b-aa22-8b453ff14a4b")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"first_name": "Test",
|
||||
"last_name": "L",
|
||||
"remarks": "",
|
||||
"mobile_phone": "",
|
||||
"email": "",
|
||||
"visitor_company": "",
|
||||
"start_time": 1688546460,
|
||||
"end_time": 1788572799,
|
||||
# NOTE: typo'ed in api docs to "Interviemw"
|
||||
"visit_reason": "Interview",
|
||||
"week_schedule": {
|
||||
"sunday": [],
|
||||
"monday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"tuesday": [],
|
||||
"wednesday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"thursday": [
|
||||
{"start_time": "11:00:00", "end_time": "18:00:59"}
|
||||
],
|
||||
"friday": [
|
||||
{"start_time": "10:00:00", "end_time": "17:00:59"}
|
||||
],
|
||||
"saturday": [],
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"id": "6ff875d2-af87-470b-9cb5-774c6596afc8",
|
||||
"type": "door",
|
||||
},
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
json={
|
||||
"code": "SUCCESS",
|
||||
"data": {
|
||||
"first_name": "H",
|
||||
"id": "8564ce90-76ba-445f-b78b-6cca39af0130",
|
||||
"last_name": "L",
|
||||
"nfc_cards": [],
|
||||
"pin_code": None,
|
||||
"resources": [
|
||||
{
|
||||
"id": "5c496423-6d25-4e4f-8cdf-95ad5135188a",
|
||||
"name": "Door-Group-1",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "d5573467-d6b3-4e8f-8e48-8a322b91664a",
|
||||
"name": "UNVR",
|
||||
"type": "door_group",
|
||||
},
|
||||
{
|
||||
"id": "e311ca94-172c-49fe-9c91-49bd8ecef845",
|
||||
"name": "visitor-1691646856144",
|
||||
"type": "door_group",
|
||||
},
|
||||
],
|
||||
"schedule": {
|
||||
"id": "c03bf601-0b90-4341-bce4-6061931e9f4e",
|
||||
"is_default": False,
|
||||
"name": "visitor-1691646856097",
|
||||
"type": "access",
|
||||
"weekly": {
|
||||
"friday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
"monday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
"saturday": [],
|
||||
"sunday": [],
|
||||
"thursday": [
|
||||
{"end_time": "18:00:59", "start_time": "11:00:00"}
|
||||
],
|
||||
"tuesday": [],
|
||||
"wednesday": [
|
||||
{"end_time": "17:00:59", "start_time": "10:00:00"}
|
||||
],
|
||||
},
|
||||
},
|
||||
"schedule_id": "c03bf601-0b90-4341-bce4-6061931e9f4e",
|
||||
"status": "ACTIVE",
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
)
|
||||
|
||||
resp = self.client.update_visitor(
|
||||
visitor_id=visitor_id,
|
||||
first_name="Test",
|
||||
last_name="L",
|
||||
remarks="",
|
||||
mobile_phone="",
|
||||
email="",
|
||||
visitor_company="",
|
||||
start_time=datetime.datetime.fromtimestamp(1688546460, tz=datetime.UTC),
|
||||
end_time=datetime.datetime.fromtimestamp(1788572799, tz=datetime.UTC),
|
||||
visit_reason=VisitReason.INTERVIEW,
|
||||
week_schedule=WeekSchedule(
|
||||
sunday=[],
|
||||
monday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
tuesday=[],
|
||||
wednesday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
thursday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(11, 0, 0),
|
||||
end_time=datetime.time(18, 0, 59),
|
||||
)
|
||||
],
|
||||
friday=[
|
||||
TimePeriod(
|
||||
start_time=datetime.time(10, 0, 0),
|
||||
end_time=datetime.time(17, 0, 59),
|
||||
)
|
||||
],
|
||||
saturday=[],
|
||||
),
|
||||
resources=[
|
||||
DoorResource(
|
||||
id=DoorId("6ff875d2-af87-470b-9cb5-774c6596afc8"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("5c496423-6d25-4e4f-8cdf-95ad5135188a"),
|
||||
),
|
||||
DoorGroupResource(
|
||||
id=DoorGroupId("d5573467-d6b3-4e8f-8e48-8a322b91664a")
|
||||
),
|
||||
],
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_delete_visitor(self) -> None:
|
||||
"""4.6 Delete Visitor"""
|
||||
visitor_id = VisitorId("c81dfee6-5970-4938-bd30-40820e16ea01")
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.query_param_matcher({"is_force": "true"}),
|
||||
],
|
||||
json={"code": "SUCCESS", "data": None, "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.delete_visitor(
|
||||
visitor_id=visitor_id,
|
||||
is_force=True,
|
||||
).success_or_raise()
|
||||
# TODO: verify correctness of data?
|
||||
|
||||
@responses.activate
|
||||
def test_assign_nfc_card_to_visitor(self) -> None:
|
||||
"""4.7 Assign NFC Card To Visitor"""
|
||||
visitor_id = VisitorId("60b5c15e-9aff-4fc8-9547-d21d2e39c1ff")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}/nfc_cards",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0",
|
||||
"force_add": True,
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_nfc_card_to_visitor(
|
||||
visitor_id=visitor_id,
|
||||
token=NfcCardToken(
|
||||
"d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0"
|
||||
),
|
||||
force_add=True,
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_unassign_nfc_card_from_visitor(self) -> None:
|
||||
"""4.8 Unassign NFC Card From Visitor"""
|
||||
visitor_id = VisitorId("60b5c15e-9aff-4fc8-9547-d21d2e39c1ff")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}/nfc_cards/delete",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher(
|
||||
{
|
||||
"token": "d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0"
|
||||
}
|
||||
),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.unassign_nfc_card_from_visitor(
|
||||
visitor_id=visitor_id,
|
||||
token=NfcCardToken(
|
||||
"d27822fc682b478dc637c6db01813e465174df6e54ca515d8427db623cfda1d0"
|
||||
),
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_assign_pin_code_to_visitor(self) -> None:
|
||||
"""4.9 Assign Pin Code To Visitor"""
|
||||
visitor_id = VisitorId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.put(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}/pin_codes",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
matchers.json_params_matcher({"pin_code": "57301208"}),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.assign_pin_code_to_visitor(
|
||||
visitor_id=visitor_id,
|
||||
pin_code="57301208",
|
||||
).success_or_raise()
|
||||
|
||||
@responses.activate
|
||||
def test_unassign_pin_code_from_visitor(self) -> None:
|
||||
"""4.10 Unassign Pin Code From Visitor"""
|
||||
visitor_id = VisitorId("17d2f099-99df-429b-becb-1399a6937e5a")
|
||||
responses.delete(
|
||||
f"https://{self.host}/api/v1/developer/visitors/{visitor_id}/pin_codes",
|
||||
match=[
|
||||
matchers.header_matcher(self.common_headers),
|
||||
],
|
||||
json={"code": "SUCCESS", "msg": "success"},
|
||||
)
|
||||
|
||||
resp = self.client.unassign_pin_code_from_visitor(
|
||||
visitor_id=visitor_id
|
||||
).success_or_raise()
|
802
uv.lock
generated
Normal file
802
uv.lock
generated
Normal file
@ -0,0 +1,802 @@
|
||||
version = 1
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.13'",
|
||||
"python_full_version >= '3.13'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dateutil" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffe"
|
||||
version = "1.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/c9/8167810358ca129839156dc002526e7398b5fad4a9d7b6e88b875e802d0d/griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b", size = 384113 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/00/e693a155da0a2a72fd2df75b8fe338146cae59d590ad6f56800adde90cb5/griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860", size = 127132 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
{ name = "ghp-import" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markdown" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "mergedeep" },
|
||||
{ name = "mkdocs-get-deps" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "pyyaml-env-tag" },
|
||||
{ name = "watchdog" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "mkdocs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-get-deps"
|
||||
version = "0.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mergedeep" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.44"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "babel" },
|
||||
{ name = "colorama" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markdown" },
|
||||
{ name = "mkdocs" },
|
||||
{ name = "mkdocs-material-extensions" },
|
||||
{ name = "paginate" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pymdown-extensions" },
|
||||
{ name = "regex" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/56/182d8121db9ab553cdf9bc58d5972b89833f60b63272f693c1f2b849b640/mkdocs_material-9.5.44.tar.gz", hash = "sha256:f3a6c968e524166b3f3ed1fb97d3ed3e0091183b0545cedf7156a2a6804c56c0", size = 3964306 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/eb/a801d00e0e210d82184aacce596906ec065422c78a7319244ba0771c4ded/mkdocs_material-9.5.44-py3-none-any.whl", hash = "sha256:47015f9c167d58a5ff5e682da37441fc4d66a1c79334bfc08d774763cacf69ca", size = 8674509 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.27.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markdown" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "mkdocs" },
|
||||
{ name = "mkdocs-autorefs" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pymdown-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/5a/5de70538c2cefae7ac3a15b5601e306ef3717290cb2aab11d51cbbc2d1c0/mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657", size = 94830 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332", size = 30658 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
python = [
|
||||
{ name = "mkdocstrings-python" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings-python"
|
||||
version = "1.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "griffe" },
|
||||
{ name = "mkdocs-autorefs" },
|
||||
{ name = "mkdocstrings" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/ec/cb6debe2db77f1ef42b25b21d93b5021474de3037cd82385e586aee72545/mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3", size = 168207 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/c1/ac524e1026d9580cbc654b5d19f5843c8b364a66d30f956372cd09fd2f92/mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a", size = 111759 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paginate"
|
||||
version = "0.5.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.23.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/0b/32f05854cfd432e9286bb41a870e0d1a926b72df5f5cdb6dec962b2e369e/pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7", size = 840790 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/53/32/95a164ddf533bd676cbbe878e36e89b4ade3efde8dd61d0148c90cbbe57e/pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77", size = 263448 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "responses"
|
||||
version = "0.25.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/24/1d67c8974daa502e860b4a5b57ad6de0d7dbc0b1160ef7148189a24a40e1/responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba", size = 77798 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/24/93293d0be0db9da1ed8dfc5e6af700fdd40e8f10a928704dd179db9f03c1/responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb", size = 55238 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20241016"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unifi-access"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "pytest" },
|
||||
{ name = "responses" },
|
||||
{ name = "ruff" },
|
||||
{ name = "types-requests" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "mkdocs-material" },
|
||||
{ name = "mkdocstrings", extra = ["python"] },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "pydantic", specifier = ">=2.9.2" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "mypy", specifier = ">=1.13" },
|
||||
{ name = "pytest", specifier = ">=8.3.3" },
|
||||
{ name = "responses", specifier = ">=0.25.3" },
|
||||
{ name = "ruff", specifier = ">=0.7.1" },
|
||||
{ name = "types-requests", specifier = ">=2.32.0.20241016" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "mkdocs-material", specifier = ">=9.5.42" },
|
||||
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.26.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "6.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 },
|
||||
]
|
Loading…
Reference in New Issue
Block a user