doorcontrol: Add syncing of members and policies with UniFi Access
This commit is contained in:
parent
431dfb723b
commit
df4abbbe2f
@ -240,6 +240,9 @@ class NonCIBase(Base):
|
||||
HID_DOOR_USERNAME = values.Value(environ_required=True, environ_prefix=None)
|
||||
HID_DOOR_PASSWORD = values.SecretValue(environ_prefix=None)
|
||||
|
||||
UNIFI_ACCESS_HOST = values.Value(environ_prefix=None)
|
||||
UNIFI_ACCESS_API_TOKEN = values.SecretValue(environ_prefix=None)
|
||||
|
||||
# TODO: should validate emails (but EmailValidator doesn't handle name parts)
|
||||
INVOICE_HANDLERS = values.ListValue(
|
||||
environ_required=True, environ_prefix="CMSMANAGE"
|
||||
|
@ -3,7 +3,14 @@ from django.contrib import admin
|
||||
from django_object_actions import DjangoObjectActions, action
|
||||
|
||||
from .forms import AttributeScheduleRuleForm, DoorAdminForm
|
||||
from .models import AttributeScheduleRule, Door, FlagScheduleRule, HIDEvent, Schedule
|
||||
from .models import (
|
||||
ActiveEventInstructorRule,
|
||||
AttributeScheduleRule,
|
||||
Door,
|
||||
FlagScheduleRule,
|
||||
HIDEvent,
|
||||
Schedule,
|
||||
)
|
||||
from .tasks.scrapehidevents import q_getMessagesAllDoors
|
||||
|
||||
|
||||
@ -19,9 +26,18 @@ class AttributeScheduleRuleInline(admin.TabularInline):
|
||||
extra = 0
|
||||
|
||||
|
||||
class ActiveEventInstructorRuleInline(admin.TabularInline):
|
||||
model = ActiveEventInstructorRule
|
||||
extra = 0
|
||||
|
||||
|
||||
@admin.register(Schedule)
|
||||
class ScheduleAdmin(admin.ModelAdmin):
|
||||
inlines = [FlagScheduleRuleInline, AttributeScheduleRuleInline]
|
||||
inlines = [
|
||||
FlagScheduleRuleInline,
|
||||
AttributeScheduleRuleInline,
|
||||
ActiveEventInstructorRuleInline,
|
||||
]
|
||||
|
||||
|
||||
@admin.register(Door)
|
||||
|
@ -9,6 +9,7 @@ def post_migrate_callback(sender, **kwargs):
|
||||
|
||||
from .tasks.scrapehidevents import q_getMessagesAllDoors
|
||||
from .tasks.update_doors import q_update_all_doors
|
||||
from .tasks.update_unifi_access import update_access
|
||||
|
||||
ensure_scheduled(
|
||||
q_getMessagesAllDoors,
|
||||
@ -22,6 +23,12 @@ def post_migrate_callback(sender, **kwargs):
|
||||
minutes=15,
|
||||
)
|
||||
|
||||
ensure_scheduled(
|
||||
update_access,
|
||||
schedule_type=Schedule.MINUTES,
|
||||
minutes=5,
|
||||
)
|
||||
|
||||
|
||||
class DoorControlConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
|
21
doorcontrol/management/commands/update_unifi_access.py
Normal file
21
doorcontrol/management/commands/update_unifi_access.py
Normal file
@ -0,0 +1,21 @@
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from doorcontrol.tasks.update_unifi_access import logger, update_access
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
|
||||
def handle(self, *args, dry_run: bool, verbosity: int, **options):
|
||||
verbosity_levels = {
|
||||
0: logging.ERROR,
|
||||
1: logging.WARNING,
|
||||
2: logging.INFO,
|
||||
3: logging.DEBUG,
|
||||
}
|
||||
logger.setLevel(verbosity_levels.get(verbosity, logging.WARNING))
|
||||
|
||||
update_access()
|
@ -0,0 +1,68 @@
|
||||
# Generated by Django 5.1.3 on 2024-11-22 04:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0002_alter_hidevent_raw_card_number"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="attributeschedulerule",
|
||||
name="name",
|
||||
field=models.CharField(
|
||||
default="",
|
||||
help_text="Used for creating/matching Unifi Access user groups. Do not change after creation without also changing name in Access.",
|
||||
max_length=255,
|
||||
unique=True,
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flagschedulerule",
|
||||
name="name",
|
||||
field=models.CharField(
|
||||
default="",
|
||||
help_text="Used for creating/matching Unifi Access user groups. Do not change after creation without also changing name in Access.",
|
||||
max_length=255,
|
||||
unique=True,
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ActiveEventInstructorRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
help_text="Used for creating/matching Unifi Access user groups. Do not change after creation without also changing name in Access.",
|
||||
max_length=255,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
("doors", models.ManyToManyField(to="doorcontrol.door")),
|
||||
(
|
||||
"schedule",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.schedule",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Self
|
||||
|
||||
from django.conf import settings
|
||||
@ -7,8 +7,8 @@ from django.db.models import OuterRef, Q, Subquery
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from membershipworks.models import EventExt, Member, MemberQuerySet
|
||||
from membershipworks.models import Flag as MembershipWorksFlag
|
||||
from membershipworks.models import Member
|
||||
|
||||
from .hid.Credential import Credential, InvalidHexCode
|
||||
from .hid.DoorController import DoorController
|
||||
@ -69,12 +69,20 @@ class Schedule(models.Model):
|
||||
|
||||
|
||||
class AbstractScheduleRule(models.Model):
|
||||
name = models.CharField(
|
||||
help_text="Used for creating/matching Unifi Access user groups. Do not change after creation without also changing name in Access.",
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
|
||||
doors = models.ManyToManyField(Door)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_matching_members(self) -> MemberQuerySet:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FlagScheduleRule(AbstractScheduleRule):
|
||||
flag = models.ForeignKey(
|
||||
@ -88,6 +96,25 @@ class FlagScheduleRule(AbstractScheduleRule):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.schedule} [flag: {self.flag}]"
|
||||
|
||||
def get_matching_members(self) -> MemberQuerySet:
|
||||
return self.flag.members.all()
|
||||
|
||||
|
||||
class ActiveEventInstructorRule(AbstractScheduleRule):
|
||||
def __str__(self) -> str:
|
||||
return "{self.schedule} [Active Instructor]"
|
||||
|
||||
# grant instructors access for ~1 hour around their class times
|
||||
def get_matching_members(self) -> MemberQuerySet:
|
||||
now = timezone.now()
|
||||
margin = timedelta(hours=1)
|
||||
active_event_instructors = EventExt.objects.filter(
|
||||
occurred=True,
|
||||
meeting_times__start__lt=now + margin,
|
||||
meeting_times__end__gt=now - margin,
|
||||
).values_list("instructor", flat=True)
|
||||
return Member.objects.filter(eventinstructor__in=active_event_instructors)
|
||||
|
||||
|
||||
class AttributeScheduleRule(AbstractScheduleRule):
|
||||
access_field = models.CharField(
|
||||
@ -100,6 +127,9 @@ class AttributeScheduleRule(AbstractScheduleRule):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.schedule} [attribute: {self.access_field}]"
|
||||
|
||||
def get_matching_members(self) -> MemberQuerySet:
|
||||
return Member.objects.filter(**{self.access_field: True})
|
||||
|
||||
|
||||
class HIDEventQuerySet(models.QuerySet):
|
||||
def with_member_id(self):
|
||||
|
180
doorcontrol/tasks/update_unifi_access.py
Normal file
180
doorcontrol/tasks/update_unifi_access.py
Normal file
@ -0,0 +1,180 @@
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from unifi_access import AccessClient
|
||||
from unifi_access.schemas import AccessPolicy, DoorResource, UserStatus
|
||||
from unifi_access.schemas import User as AccessUser
|
||||
|
||||
from cmsmanage.django_q2_helper import q_task_group
|
||||
from doorcontrol.models import (
|
||||
AbstractScheduleRule,
|
||||
ActiveEventInstructorRule,
|
||||
AttributeScheduleRule,
|
||||
Door,
|
||||
FlagScheduleRule,
|
||||
Schedule,
|
||||
)
|
||||
from membershipworks.models import Member
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_user_groups(
|
||||
unifi_access: AccessClient,
|
||||
access_users_by_employee_number: Mapping[str, AccessUser],
|
||||
) -> None:
|
||||
groups = unifi_access.fetch_all_user_groups()
|
||||
top_group = next(g for g in groups if g.up_id == "")
|
||||
rule_groups = {g.name: g for g in groups if g.up_id == top_group.id}
|
||||
|
||||
access_policies_by_name = {
|
||||
p.name: p for p in unifi_access.fetch_all_access_policies()
|
||||
}
|
||||
schedules_by_name = {s.name: s for s in unifi_access.fetch_all_schedules()}
|
||||
doors_by_name = {d.name: d for d in unifi_access.fetch_all_doors()}
|
||||
|
||||
def sync_access_policy(schedule: Schedule, door: Door) -> AccessPolicy:
|
||||
access_policy_name = f"{rule.schedule.name} | {door.name}"
|
||||
expected_access_policy = {
|
||||
"name": access_policy_name,
|
||||
"resources": (
|
||||
[DoorResource(id=doors_by_name[door.name].id)]
|
||||
if door.name in doors_by_name
|
||||
else []
|
||||
),
|
||||
"schedule_id": schedules_by_name[schedule.name].id,
|
||||
}
|
||||
if access_policy := access_policies_by_name.get(access_policy_name):
|
||||
changes = {
|
||||
k: v
|
||||
for k, v in expected_access_policy.items()
|
||||
if getattr(access_policy, k) != v
|
||||
}
|
||||
if changes:
|
||||
logger.debug(
|
||||
" - updating access policy %s: %s", access_policy_name, changes
|
||||
)
|
||||
access_policy = unifi_access.update_access_policy(
|
||||
access_policy.id, **changes
|
||||
)
|
||||
access_policies_by_name[access_policy_name] = access_policy
|
||||
|
||||
else:
|
||||
logger.debug(
|
||||
" - creating access policy %s: %s",
|
||||
access_policy_name,
|
||||
expected_access_policy,
|
||||
)
|
||||
access_policy = unifi_access.create_access_policy(**expected_access_policy)
|
||||
access_policies_by_name[access_policy_name] = access_policy
|
||||
|
||||
return access_policy
|
||||
|
||||
def update_groups_for_rule(rule: AbstractScheduleRule) -> None:
|
||||
assert rule.name
|
||||
logger.info("Syncing user group '%s'", rule.name)
|
||||
rule_group = rule_groups.get(rule.name)
|
||||
if not rule_group:
|
||||
logger.debug(" - creating top level group for rule")
|
||||
rule_group = unifi_access.create_user_group(rule.name)
|
||||
# might be None if it already existed, but we already checked for that earlier
|
||||
assert rule_group
|
||||
|
||||
door_groups = {g.name: g for g in groups if g.up_id == rule_group.id}
|
||||
for door in rule.doors.all():
|
||||
door_rule_group = door_groups.get(door.name)
|
||||
if not door_rule_group:
|
||||
logger.debug(" - creating child group for door %s", door.name)
|
||||
door_rule_group = unifi_access.create_user_group(
|
||||
door.name, up_id=rule_group.id
|
||||
)
|
||||
# might be None if it already existed, but we already checked for that earlier
|
||||
assert door_rule_group
|
||||
|
||||
access_policy = sync_access_policy(rule.schedule, door)
|
||||
unifi_access.assign_access_policy_to_user_group(
|
||||
door_rule_group.id, [access_policy.id]
|
||||
)
|
||||
|
||||
expected_group_members = (
|
||||
rule.get_matching_members()
|
||||
.with_is_active()
|
||||
.filter(
|
||||
Q(**{door.access_field: True})
|
||||
& (
|
||||
Q(is_active=True)
|
||||
| Member.objects.has_flag("folder", "Misc. Access")
|
||||
)
|
||||
)
|
||||
)
|
||||
# all members should exist in Access by this point
|
||||
expected_group_member_ids = [
|
||||
access_users_by_employee_number[member.uid].id
|
||||
for member in expected_group_members
|
||||
]
|
||||
if expected_group_member_ids:
|
||||
unifi_access.assign_user_to_user_group(
|
||||
door_rule_group.id, expected_group_member_ids
|
||||
)
|
||||
|
||||
for rule in FlagScheduleRule.objects.all():
|
||||
update_groups_for_rule(rule)
|
||||
for rule in AttributeScheduleRule.objects.all():
|
||||
update_groups_for_rule(rule)
|
||||
# TODO: this could probably be done better by creating temporary
|
||||
# schedules for active events
|
||||
for rule in ActiveEventInstructorRule.objects.all():
|
||||
update_groups_for_rule(rule)
|
||||
|
||||
|
||||
def sync_members(access_client: AccessClient):
|
||||
access_users_by_employee_number = {
|
||||
user.employee_number: user
|
||||
for user in access_client.fetch_all_users__unpaged()
|
||||
if user.employee_number
|
||||
}
|
||||
|
||||
for member in Member.objects.with_is_active().all():
|
||||
logger.info("Syncing member %s", member)
|
||||
expected_user = {
|
||||
"first_name": member.first_name,
|
||||
"last_name": member.last_name,
|
||||
# TODO: omitted to avoid spamming members for now
|
||||
# "user_email": member.email,
|
||||
"employee_number": member.uid,
|
||||
}
|
||||
|
||||
if access_user := access_users_by_employee_number.get(member.uid):
|
||||
expected_user["status"] = (
|
||||
UserStatus.ACTIVE if member.is_active else UserStatus.DEACTIVATED
|
||||
)
|
||||
changes = {
|
||||
k: v for k, v in expected_user.items() if getattr(access_user, k) != v
|
||||
}
|
||||
if changes:
|
||||
logger.debug(" - updating, changes: %s", changes)
|
||||
access_client.update_user(
|
||||
**changes,
|
||||
user_id=access_user.id,
|
||||
)
|
||||
|
||||
else:
|
||||
logger.debug(" - creating user: %s", expected_user)
|
||||
access_client.register_user(**expected_user)
|
||||
|
||||
update_user_groups(access_client, access_users_by_employee_number)
|
||||
|
||||
|
||||
@q_task_group("Update UniFi Access Data")
|
||||
def update_access():
|
||||
access_client = AccessClient(
|
||||
host=settings.UNIFI_ACCESS_HOST,
|
||||
api_token=settings.UNIFI_ACCESS_API_TOKEN,
|
||||
# TODO: fix SSL cert
|
||||
verify=False,
|
||||
)
|
||||
|
||||
sync_members(access_client)
|
259
pdm.lock
generated
259
pdm.lock
generated
@ -5,7 +5,7 @@
|
||||
groups = ["default", "debug", "dev", "lint", "server", "typing"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:47e7818f755b3b8636d346c37afd7287cec977605c1bcdb3eccbb78e6e2823af"
|
||||
content_hash = "sha256:3426371550c0b7215623ca93958c30dfa60f948c234edfdea80e7a43941abfc7"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = "==3.11.*"
|
||||
@ -15,35 +15,36 @@ gil_disabled = false
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
version = "2.4.3"
|
||||
version = "2.4.4"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Happy Eyeballs for asyncio"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"},
|
||||
{file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"},
|
||||
{file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"},
|
||||
{file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.10.10"
|
||||
requires_python = ">=3.8"
|
||||
version = "3.11.10"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Async http client/server framework (asyncio)"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"aiohappyeyeballs>=2.3.0",
|
||||
"aiosignal>=1.1.2",
|
||||
"async-timeout<5.0,>=4.0; python_version < \"3.11\"",
|
||||
"async-timeout<6.0,>=4.0; python_version < \"3.11\"",
|
||||
"attrs>=17.3.0",
|
||||
"frozenlist>=1.1.1",
|
||||
"multidict<7.0,>=4.5",
|
||||
"yarl<2.0,>=1.12.0",
|
||||
"propcache>=0.2.0",
|
||||
"yarl<2.0,>=1.17.0",
|
||||
]
|
||||
files = [
|
||||
{file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"},
|
||||
{file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"},
|
||||
{file = "aiohttp-3.11.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f5635f7b74bcd4f6f72fcd85bea2154b323a9f05226a80bc7398d0c90763b0"},
|
||||
{file = "aiohttp-3.11.10.tar.gz", hash = "sha256:b1fc6b45010a8d0ff9e88f9f2418c6fd408c99c211257334aff41597ebece42e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -61,9 +62,24 @@ files = [
|
||||
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Reusable constraint types to use with typing.Annotated"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"typing-extensions>=4.0.0; python_version < \"3.9\"",
|
||||
]
|
||||
files = [
|
||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.6.2.post1"
|
||||
version = "4.7.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
groups = ["server"]
|
||||
@ -72,11 +88,11 @@ dependencies = [
|
||||
"exceptiongroup>=1.0.2; python_version < \"3.11\"",
|
||||
"idna>=2.8",
|
||||
"sniffio>=1.1",
|
||||
"typing-extensions>=4.1; python_version < \"3.11\"",
|
||||
"typing-extensions>=4.5; python_version < \"3.13\"",
|
||||
]
|
||||
files = [
|
||||
{file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"},
|
||||
{file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"},
|
||||
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
|
||||
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -96,17 +112,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.4.1"
|
||||
version = "3.0.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Annotate AST trees with source code positions"
|
||||
groups = ["dev"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"six>=1.12.0",
|
||||
"typing; python_version < \"3.5\"",
|
||||
]
|
||||
files = [
|
||||
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
|
||||
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
|
||||
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
|
||||
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -269,14 +282,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.8"
|
||||
version = "7.6.9"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Code coverage measurement for Python"
|
||||
groups = ["dev"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
|
||||
{file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
|
||||
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -964,20 +977,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.54.1"
|
||||
version = "4.55.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Tools to manipulate font files"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"},
|
||||
{file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"},
|
||||
{file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"},
|
||||
{file = "fonttools-4.55.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:131591ac8d7a47043aaf29581aba755ae151d46e49d2bf49608601efd71e8b4d"},
|
||||
{file = "fonttools-4.55.2-py3-none-any.whl", hash = "sha256:8e2d89fbe9b08d96e22c7a81ec04a4e8d8439c31223e2dc6f2f9fc8ff14bdf9f"},
|
||||
{file = "fonttools-4.55.2.tar.gz", hash = "sha256:45947e7b3f9673f91df125d375eb57b9a23f2a603f438a1aebf3171bffa7a205"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.54.1"
|
||||
version = "4.55.2"
|
||||
extras = ["woff"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "Tools to manipulate font files"
|
||||
@ -986,13 +999,13 @@ marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"brotli>=1.0.1; platform_python_implementation == \"CPython\"",
|
||||
"brotlicffi>=0.8.0; platform_python_implementation != \"CPython\"",
|
||||
"fonttools==4.54.1",
|
||||
"fonttools==4.55.2",
|
||||
"zopfli>=0.1.4",
|
||||
]
|
||||
files = [
|
||||
{file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"},
|
||||
{file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"},
|
||||
{file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"},
|
||||
{file = "fonttools-4.55.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:131591ac8d7a47043aaf29581aba755ae151d46e49d2bf49608601efd71e8b4d"},
|
||||
{file = "fonttools-4.55.2-py3-none-any.whl", hash = "sha256:8e2d89fbe9b08d96e22c7a81ec04a4e8d8439c31223e2dc6f2f9fc8ff14bdf9f"},
|
||||
{file = "fonttools-4.55.2.tar.gz", hash = "sha256:45947e7b3f9673f91df125d375eb57b9a23f2a603f438a1aebf3171bffa7a205"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1010,7 +1023,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.22.0"
|
||||
version = "2.23.0"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Google API client core library"
|
||||
groups = ["default", "typing"]
|
||||
@ -1024,8 +1037,8 @@ dependencies = [
|
||||
"requests<3.0.0.dev0,>=2.18.0",
|
||||
]
|
||||
files = [
|
||||
{file = "google_api_core-2.22.0-py3-none-any.whl", hash = "sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021"},
|
||||
{file = "google_api_core-2.22.0.tar.gz", hash = "sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35"},
|
||||
{file = "google_api_core-2.23.0-py3-none-any.whl", hash = "sha256:c20100d4c4c41070cf365f1d8ddf5365915291b5eb11b83829fbd1c999b5122f"},
|
||||
{file = "google_api_core-2.23.0.tar.gz", hash = "sha256:2ceb087315e6af43f256704b871d99326b1f12a9d6ce99beaedec99ba26a0ace"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1066,7 +1079,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.35.0"
|
||||
version = "2.36.0"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Google Authentication Library"
|
||||
groups = ["default", "typing"]
|
||||
@ -1077,8 +1090,8 @@ dependencies = [
|
||||
"rsa<5,>=3.1.4",
|
||||
]
|
||||
files = [
|
||||
{file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"},
|
||||
{file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"},
|
||||
{file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"},
|
||||
{file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1114,7 +1127,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Common protobufs used in Google APIs"
|
||||
groups = ["default", "typing"]
|
||||
@ -1123,8 +1136,8 @@ dependencies = [
|
||||
"protobuf!=3.20.0,!=3.20.1,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.20.2",
|
||||
]
|
||||
files = [
|
||||
{file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"},
|
||||
{file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"},
|
||||
{file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"},
|
||||
{file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1243,17 +1256,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.1"
|
||||
version = "0.19.2"
|
||||
requires_python = ">=3.6"
|
||||
summary = "An autocompletion tool for Python that can be used for text editors."
|
||||
groups = ["dev"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"parso<0.9.0,>=0.8.3",
|
||||
"parso<0.9.0,>=0.8.4",
|
||||
]
|
||||
files = [
|
||||
{file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
|
||||
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
|
||||
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
|
||||
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1272,14 +1285,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.9.25"
|
||||
requires_python = ">=3.8"
|
||||
version = "0.10.0"
|
||||
requires_python = ">=3.8.0"
|
||||
summary = "A Python implementation of the JSON5 data format."
|
||||
groups = ["lint"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"},
|
||||
{file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"},
|
||||
{file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"},
|
||||
{file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1516,14 +1529,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
version = "24.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Core utilities for Python packages"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1594,15 +1607,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "propcache"
|
||||
version = "0.2.0"
|
||||
requires_python = ">=3.8"
|
||||
version = "0.2.1"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Accelerated property cache"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"},
|
||||
{file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"},
|
||||
{file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"},
|
||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"},
|
||||
{file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"},
|
||||
{file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1622,15 +1635,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "5.28.3"
|
||||
version = "5.29.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = ""
|
||||
groups = ["default", "typing"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"},
|
||||
{file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"},
|
||||
{file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"},
|
||||
{file = "protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d"},
|
||||
{file = "protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0"},
|
||||
{file = "protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1663,7 +1676,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-pool"
|
||||
version = "3.2.3"
|
||||
version = "3.2.4"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Connection Pool for Psycopg"
|
||||
groups = ["default"]
|
||||
@ -1672,8 +1685,8 @@ dependencies = [
|
||||
"typing-extensions>=4.6",
|
||||
]
|
||||
files = [
|
||||
{file = "psycopg_pool-3.2.3-py3-none-any.whl", hash = "sha256:53bd8e640625e01b2927b2ad96df8ed8e8f91caea4597d45e7673fc7bbb85eb1"},
|
||||
{file = "psycopg_pool-3.2.3.tar.gz", hash = "sha256:bb942f123bef4b7fbe4d55421bd3fb01829903c95c0f33fd42b7e94e5ac9b52a"},
|
||||
{file = "psycopg_pool-3.2.4-py3-none-any.whl", hash = "sha256:f6a22cff0f21f06d72fb2f5cb48c618946777c49385358e0c88d062c59cbd224"},
|
||||
{file = "psycopg_pool-3.2.4.tar.gz", hash = "sha256:61774b5bbf23e8d22bedc7504707135aaf744679f8ef9b3fe29942920746a6ed"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1755,6 +1768,38 @@ files = [
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.3"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Data validation using Python type hints"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"annotated-types>=0.6.0",
|
||||
"pydantic-core==2.27.1",
|
||||
"typing-extensions>=4.12.2",
|
||||
]
|
||||
files = [
|
||||
{file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
|
||||
{file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Core functionality for Pydantic validation and serialization"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"typing-extensions!=4.7.0,>=4.6.0",
|
||||
]
|
||||
files = [
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
|
||||
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydyf"
|
||||
version = "0.11.0"
|
||||
@ -1874,14 +1919,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.9.11"
|
||||
version = "2024.11.6"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Alternative regular expression module, to replace re."
|
||||
groups = ["lint"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"},
|
||||
{file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
|
||||
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1988,14 +2033,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "1.17.0"
|
||||
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
summary = "Python 2 and 3 compatibility utilities"
|
||||
groups = ["default", "dev", "lint"]
|
||||
groups = ["default", "lint"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2035,14 +2080,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A non-validating SQL parser."
|
||||
groups = ["default", "debug", "dev", "typing"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
|
||||
{file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
|
||||
{file = "sqlparse-0.5.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"},
|
||||
{file = "sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2150,7 +2195,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.6"
|
||||
version = "4.67.1"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Fast, Extensible Progress Meter"
|
||||
groups = ["lint"]
|
||||
@ -2159,8 +2204,8 @@ dependencies = [
|
||||
"colorama; platform_system == \"Windows\"",
|
||||
]
|
||||
files = [
|
||||
{file = "tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63"},
|
||||
{file = "tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090"},
|
||||
{file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
|
||||
{file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2207,14 +2252,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "types-docutils"
|
||||
version = "0.21.0.20241005"
|
||||
version = "0.21.0.20241128"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Typing stubs for docutils"
|
||||
groups = ["typing"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "types-docutils-0.21.0.20241005.tar.gz", hash = "sha256:48f804a2b50da3a1b1681c4ca1b6184416a6e4129e302d15c44e9d97c59b3365"},
|
||||
{file = "types_docutils-0.21.0.20241005-py3-none-any.whl", hash = "sha256:4d9021422f2f3fca8b0726fb8949395f66a06c0d951479eb3b1387d75b134430"},
|
||||
{file = "types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039"},
|
||||
{file = "types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2339,14 +2384,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "75.2.0.20241025"
|
||||
version = "75.6.0.20241126"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Typing stubs for setuptools"
|
||||
groups = ["typing"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "types-setuptools-75.2.0.20241025.tar.gz", hash = "sha256:2949913a518d5285ce00a3b7d88961c80a6e72ffb8f3da0a3f5650ea533bd45e"},
|
||||
{file = "types_setuptools-75.2.0.20241025-py3-none-any.whl", hash = "sha256:6721ac0f1a620321e2ccd87a9a747c4a383dc381f78d894ce37f2455b45fcf1c"},
|
||||
{file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"},
|
||||
{file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2365,7 +2410,7 @@ name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
groups = ["default", "dev", "typing"]
|
||||
groups = ["default", "dev", "server", "typing"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
@ -2391,6 +2436,22 @@ files = [
|
||||
{file = "udm_rest_client-1.2.3-py2.py3-none-any.whl", hash = "sha256:ee29e94e3ba5fba63a694e33d119b1af7450afcdce3a44301d4cd5ddfa1f980b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unifi-access"
|
||||
version = "0.1.0"
|
||||
requires_python = ">=3.11"
|
||||
summary = "Typed wrapper for the Unifi Access Public API"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"pydantic>=2.10.1",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
files = [
|
||||
{file = "unifi_access-0.1.0-py3-none-any.whl", hash = "sha256:a901099d8b900e0266fce27a8d9a4bf4aee53fe5edf9a4110a7f45431599f7d4"},
|
||||
{file = "unifi_access-0.1.0.tar.gz", hash = "sha256:6a61a491f9d36d2208bbf73da0e3bb303f385c0b22a708900a4000b58d4532af"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uritemplate"
|
||||
version = "4.1.1"
|
||||
@ -2469,8 +2530,8 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.24.0"
|
||||
requires_python = ">=3.8"
|
||||
version = "1.0.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Simple, modern and high performance file watching and code reload in python."
|
||||
groups = ["server"]
|
||||
marker = "python_version == \"3.11\""
|
||||
@ -2478,8 +2539,8 @@ dependencies = [
|
||||
"anyio>=3.0.0",
|
||||
]
|
||||
files = [
|
||||
{file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"},
|
||||
{file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245fab124b9faf58430da547512d91734858df13f2ddd48ecfa5e493455ffccb"},
|
||||
{file = "watchfiles-1.0.0.tar.gz", hash = "sha256:37566c844c9ce3b5deb964fe1a23378e575e74b114618d211fbda8f59d7b5dab"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2543,20 +2604,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "13.1"
|
||||
requires_python = ">=3.8"
|
||||
version = "14.1"
|
||||
requires_python = ">=3.9"
|
||||
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
groups = ["server"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"},
|
||||
{file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"},
|
||||
{file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"},
|
||||
{file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"},
|
||||
{file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
|
||||
{file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarl"
|
||||
version = "1.17.1"
|
||||
version = "1.18.3"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Yet another URL library"
|
||||
groups = ["default"]
|
||||
@ -2567,9 +2628,9 @@ dependencies = [
|
||||
"propcache>=0.2.0",
|
||||
]
|
||||
files = [
|
||||
{file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d"},
|
||||
{file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"},
|
||||
{file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"},
|
||||
{file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"},
|
||||
{file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"},
|
||||
{file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -56,6 +56,7 @@ dependencies = [
|
||||
"semver~=3.0",
|
||||
"tablib[ods,xlsx]~=3.7",
|
||||
"udm-rest-client~=1.2",
|
||||
"unifi-access~=0.1",
|
||||
"weasyprint~=63.0",
|
||||
]
|
||||
optional-dependencies.server = [
|
||||
@ -105,7 +106,7 @@ name = "pypi"
|
||||
url = "https://git.claremontmakerspace.org/api/packages/CMS/pypi/simple"
|
||||
verify_ssl = true
|
||||
name = "CMS"
|
||||
include_packages = [ "openapi-client-udm" ]
|
||||
include_packages = [ "openapi-client-udm", "unifi-access" ]
|
||||
exclude_packages = [ "*" ]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
|
Loading…
Reference in New Issue
Block a user