Compare commits

..

65 Commits

Author SHA1 Message Date
lebaudantoine 06c4e277db ⚡️(frontend) serve illustrations as WebP with a PNG fallback
The three intro slider illustrations were the heaviest part of the
homepage. On a cold load of the production homepage (cache disabled),
the browser transferred 2.67 MB in total, of which 2.00 MB was these
three PNG files: 75% of the page weight, and about 3.5 times all the
JavaScript combined (579 kB).

HTTP compression cannot help here, PNG being already compressed. The
files are heavy because of the grain texture applied on the
illustrations, which a lossless format has to store pixel by pixel.

Encoded as WebP at quality 90, which preserves the grain, the three
slides go from 2.00 MB to 182 kB (-91%). The homepage cold load drops
from 2.67 MB to about 0.86 MB (-68%), and the illustrations now
account for about 21% of the transfer instead of 75%.

The fourth illustration, used on the recording screens, gets the same
treatment (721 kB to 84 kB).

We checked in PostHog that most of our users are on browser versions
supporting WebP. For the remaining ones, a new Picture primitive
renders a <picture> element with the WebP file as a source and the
original PNG as the <img> fallback, so nobody loses the illustrations.

Add webp to the nginx static assets location, otherwise the new files
would be served without the 30 days cache headers.
2026-11-14 14:10:51 +01:00
lebaudantoine b6b3ce28db ♿️(frontend) name settings tabs on small screens
Below 800px the settings tabs only render their icon, which left
each tab, and the tab panel it labels, without an accessible name.
Screen reader users could not tell which settings section they
were opening.

Set aria-label on each tab with its existing translated label.
On wide screens it matches the visible text, so nothing changes.

Fixes #1855
2026-11-14 01:05:10 +01:00
lebaudantoine f27f2fb7de ♿️(frontend) raise contrast of inactive intro slider dots
Inactive dots of the home intro slider were 1.57:1 on white, below
the 3:1 RGAA 3.3 asks for, so low-vision users could not tell how
many slides exist.

They are now hollow: a primary.100 fill with a primary.500 outline
at 4.22:1 on white. This passes without darkening the dots so much
that they blend with the active one. Dots grow from 6px to 8px to
keep the outline legible.

Fixes #1851
2026-11-14 01:05:10 +01:00
lebaudantoine 061025b928 ♿️(frontend) structure connection test cards as lists
The step cards of each connection test section are a series of
similar items but were rendered as plain divs, so assistive
technologies could not announce them as a list or give the item
count.

Render each series as a ul with one li per card. The heading and
accordion stay inside the li. No visual change.

Fixes #1903
2026-11-14 01:05:10 +01:00
lebaudantoine 100044eeaa ♿️(frontend) warn before language change in settings
Selecting a language in the settings modal translates the whole
interface immediately. Nothing announced this beforehand, so a
screen reader user could end up in a language they don't read,
with the dialog title already announced in it.

Add a visible description under the "Langue de l'application"
field, linked to the select trigger with aria-describedby, stating
that the change applies to the whole interface on selection.

This satisfies RGAA 7.4 (WCAG 3.2.2 On Input) without adding an
extra confirmation step.

Fixes #1859
2026-11-14 01:05:10 +01:00
lebaudantoine e158b4faaf ♿️(frontend) fix grey text contrast on the connection test page
Zero counters, pending and skipped labels and step summaries used
greys below the 4.5:1 minimum of RGAA 3.2. They now use greyscale.600,
which passes on white, hovered and expanded rows.

Fixes #1897
2026-11-14 01:05:10 +01:00
lebaudantoine 3a1f0a342d ♿️(frontend) mark the language of each option in language selects
Language names are listed in their own language ("English",
"Deutsch", "Nederlands"...), but nothing told assistive technologies
so. Screen readers pronounced them with the rules of the interface
language, which can make them hard to recognise.

Select items accept an optional `lang`, rendered on a span around
the label. useLanguageLabels sets it for every language it returns.

RGAA 8.7

Fixes #1861
2026-11-13 23:38:16 +01:00
lebaudantoine 62571cd9fc ♿️(frontend) declare a background color with the body text color
Text colors were set without a matching background on the document.
With custom browser or system colors, the user's background could
end up behind our text color and make it unreadable.

Set both the default background and text colors on body, so they
are always defined as a pair.

RGAA 10.5

Fixes #1863
2026-11-13 23:38:05 +01:00
lebaudantoine 8fccf46429 ♿️(frontend) give the transcript language button an explicit name
In the transcript panel, the button showing the meeting language
opens the transcription settings, but its accessible name was only
the language itself, e.g. "Français (fr)". Screen reader users had no
way to know it changes the meeting language.

Add an aria-label that keeps the language and states the action. The
visible label is unchanged. Translated in all six locales.

RGAA 7.1

Fixes #1868
2026-11-13 23:37:48 +01:00
lebaudantoine 9481350383 ♿️(frontend) use level 2 headings in the recording side panels
The side panel already exposes its title as a level 1 heading. The
transcript and screen recording panels rendered their intro line as a
second h1, so screen reader users navigating by headings could not
tell the panel title from its subtitle.

Render both intro headings as h2 while keeping the h1 visual style,
so the hierarchy is correct and nothing changes on screen.

RGAA 9.1

Fixes #1882
2026-11-13 23:29:39 +01:00
lebaudantoine e9a58d270b 🚸(frontend) bring raised hands into view in the carousel
A raised hand matters as much as someone speaking, but it went
unnoticed when the tile was scrolled out of the carousel. It is now
brought to the top like a new speaker and stays visible while raised.
2026-11-13 23:26:49 +01:00
lebaudantoine e3351075aa 🚸(frontend) bring new speakers to the top of the carousel
In the focus layout, when someone off screen started speaking, their
tile replaced whichever tile was evicted, often at the bottom of the
column. A screen share that started did the same. Users had to scan
the whole column to find who was talking, and the spot changed every
time.

New speakers now always appear at the top, right below the local
camera and the screen shares, and the other tiles slide down by one.
The most relevant tiles are where the eye already is, and the column
moves in a predictable way.

The ordering still relies on LiveKit's sort and stable update; only
the position of the incoming tile changes. This is the enhancement
#1805 was after.
2026-11-13 23:26:48 +01:00
lebaudantoine 68198e9961 🐛(frontend) promote the active speaker in the carousel layout
Regression: in the focus layout, a participant who started speaking
while their tile was outside the visible part of the carousel was no
longer brought into view.

Cause: 81f23aeb moved useTracks in StageLayout to `updateOnlyOn: []`
so the whole stage stops re-rendering on every ActiveSpeakersChanged.
useVisualStableUpdate only promotes speakers when its host renders, so
it needs something to wake it up. The grid layout got that with
useSpeakerPromotionTrigger in the same commit; the carousel did not,
and nothing else re-renders it on speaker events.

Fix: the carousel now uses the same tripwire. Unlike the grid, which
passes its current page, the carousel renders every tile in a scroll
container, so only the first maxVisibleTiles are passed as visible.

The tripwire also takes the full list of tiles the layout shows and
ignores speakers who have none. In the focus layout the pinned camera
is rendered by the focus view, not the carousel: without this, each
speaker event from that participant would force a render that cannot
promote anyone. The grid passes all its tracks, so its behaviour is
unchanged.

Known limit, not addressed here: when a single tile fits, it stays on
the local camera, which useVisualStableUpdate sorts first, so a remote
speaker cannot be promoted and the tripwire keeps firing for them.

This supersedes #1805, which will be closed. On top of this fix, it
pinned the active speaker to a fixed slot next to the local camera,
first with a 2s hold, a reserved slot and an eligibility check
(867a14d0), then simplified to the last remote speaker with no timer
(378e3d01). That is an enhancement rather than part of the fix, and it
is left out here to keep the regression fix small. It can come back in
a dedicated PR; it would also cover the single-tile case above.
2026-11-13 23:26:48 +01:00
lebaudantoine e3140e0300 🐛(frontend) apply the sans font token on every page
Since routes are lazy loaded, the LiveKit stylesheet only ships
with the room chunk. It was the only rule applying `--fonts-sans`
to the document, so pages outside a room fell back to Panda's
preflight system font stack, ignoring custom CSS themes (e.g.
Marianne in the DINUM image) and accessibility fonts.

Point Panda's `--global-font-body` at `--fonts-sans` so the
document font follows the token regardless of which chunks are
loaded.
2026-11-13 23:26:46 +01:00
lebaudantoine eb345610a7 🐛(backend) allow muting when everyone_can_mute is stored as null
A stored null was read as disabled by CanMuteParticipant, since
`.get(key, True)` only defaults when the key is missing, while the
frontend reads null as enabled. Only an explicit `false` now
disables participant muting.

Fixes #1942
Supersedes #1944
2026-11-13 01:31:33 +01:00
lebaudantoine 48d144877a 🍱(frontend) add Portuguese glyphs to Marianne subsetted fonts
The Marianne subsets were generated with a French-only whitelist, so
most Portuguese accented letters were missing. Words like "não",
"ação" or "país" fell back to the system font for single letters,
which looked broken in the middle of Marianne text.

Regenerate the 12 files from the full Marianne sources tracked in
numerique-gouv/lasuite-landingpage (src/fonts/sources), using the
glyphhanger 5.0.0 command documented there, with these characters
added to the whitelist:

- letters: Á Â Ã Í Ó Õ Ú Ü á ã í ó õ ú ü
- ordinal indicators: º ª
- punctuation: – — ‘

The previous files came from an older whitelist, so this also brings
in the characters added upstream since then (ä ł ń $ ~). No glyph
was removed. Each file grows by roughly 1 KB.
2026-11-13 00:29:07 +01:00
Railton Vasconcelos 5b6ed14a31 🌐(i18n) add Portuguese language support
Add Portuguese (pt / pt-BR) localization across the frontend UI
(all 11 i18next namespaces) and backend Django catalog (pt_BR).

Contributed by the Alan Turing Laboratory (ATLab) at the Federal
University of Ceara (UFC), Brazil.
2026-11-13 00:07:32 +01:00
lebaudantoine 6f8f5d0386 🐛(frontend) accept meeting links with query params in join dialog
Meeting links inserted into events by the Outlook calendar add-in carry
tracking query parameters, e.g. `?from=outlook-addin` or `utm_*`.
Pasting such a link into the "Join a meeting" dialog failed: the
validation regex was anchored on the room id, so any trailing query
string made the link invalid, and the submit handler only stripped the
origin, so the query string would have ended up in the room id.

Parse the input with the URL API in a new `extractRoomId` helper, used
by both validation and submit. It accepts a bare room id or a link on
the current origin (trailing slash allowed), reads the room id from the
pathname only, and returns the query string and fragment separately so
they are kept when redirecting to the room (utm tracking is preserved).
2026-11-12 23:50:05 +01:00
lebaudantoine 25122deba2 🔒️(docker) move frontend to nginx/nginx-unprivileged image
The nginxinc/nginx-unprivileged image is no longer rebuilt and
ships libxslt 1.1.43-r3, affected by CVE-2026-125350,
CVE-2026-87636 and CVE-2026-9947.

Use nginx/nginx-unprivileged in both frontend images. Its
packages are up to date, so drop the manual package upgrades.
2026-11-12 23:29:14 +01:00
briquet 9ede799cb9 🔒️(docker) upgrade musl-utils to fix CVE-2026-107891
Update musl-utils in all alpine images
2026-11-07 00:02:49 +01:00
briquet cc4275c23c 👷(ci) ignore sqlite and zlib CVEs in trivy
Ignore CVE-2026-119374 and CVE-2026-119379 (libsqlite3-0) and
CVE-2026-85091 (zlib1g) until 2026-12-05: Debian trixie ships no
fixed version yet.
2026-11-06 22:14:51 +01:00
briquet d1489f8a61 📌(summary) pin kombu to keep celery workers compatible
Pin kombu to 5.6.2 to avoid problem with redis-py that require
a more recent version of Redis >= 6.X
2026-11-06 22:14:51 +01:00
Roee Sivan c6beb8ed3e ♿️(frontend) close the invite dialog with the Escape key
The "Your meeting is ready" dialog is a react-aria Dialog rendered
without a ModalOverlay, so nothing handled Escape. It stays non-modal
on purpose, so the meeting remains usable while it is open.

Reuse useEscapeToClose, as the side panel does, so Escape only closes
the dialog when focus is inside it and does not take the key from the
chat, the side panel or menus.

The dialog opens on its own when joining, so there is no trigger to
restore focus to. Both the close button and Escape now move focus to
the first control bar button instead of letting it fall back to body.
2026-11-05 22:38:01 +01:00
Florent Chehab 505c55d3b9 📝(changelog) document summary privacy improvements
Record signed URL protection, secret-aware validation errors and safer
logging and Sentry capture under Unreleased.

Document the retention of redacted frame locals and application DEBUG
diagnostics.
2026-11-05 20:18:34 +01:00
Florent Chehab 72d964656c 🔒️(summary) scrub Sentry events and breadcrumbs
Redact signed URLs and remove untrusted exception messages, request
bodies and breadcrumb payloads from Sentry events and transactions.

Preserve stack-frame locals after the existing content scrubber runs.
Cover real HTTP failures, chained exceptions and DEBUG breadcrumbs.
2026-11-05 20:18:34 +01:00
Florent Chehab 58bf9a7d82 🔒️(summary) hide sensitive Celery task details
Publish redacted task argument representations and filter task results
and exception details from INFO-and-above Celery trace logs.

Keep task identity and DEBUG diagnostics available.
2026-11-05 20:18:34 +01:00
Florent Chehab 72af9d629e 🔒️(summary) sanitize analytics client failures
Avoid chaining raw analytics client errors or logging feature-flag
exception messages. Preserve event metadata and feature-flag fallback
behavior.
2026-11-05 20:18:34 +01:00
Florent Chehab 3cd2cb5591 🔒️(summary) sanitize LLM failures and summary progress logs
Raise safe LLM errors for upstream failures and invalid structured
responses without retaining the original exception chain.

Keep generated section titles out of INFO progress logs and preserve
the existing DEBUG diagnostics.
2026-11-05 20:18:34 +01:00
Florent Chehab fcd37b0f26 🔒️(summary) sanitize upstream HTTP failures
Replace raw download, transcription, webhook and Docs exceptions with
safe messages while preserving retryable exception categories.

Retain document IDs and DEBUG responses. Give webhook regression tests
their own tenant settings instead of relying on development tenants.
2026-11-05 20:18:34 +01:00
Florent Chehab b6a8ae7d35 🔒️(summary) clean validation errors at application boundaries
Use cleaned validation errors for task execution, prerun signals,
transcription responses and API results. Share the same cleanup with
FastAPI validation responses while retaining ordinary input values.

Verify secret masking, preserved diagnostics and summary retries.
2026-11-05 20:18:34 +01:00
Florent Chehab 68848c0d6f 🔒️(summary) protect signed URL model fields with SecretStr
Validate signed URLs as secrets and reveal them explicitly for file
access, Celery messages, webhook delivery and authorized API responses.

Cover model representations, nested broker payloads and URL consumers.
2026-11-05 20:18:34 +01:00
Florent Chehab 2ea5c85436 🔒️(summary) add secret-aware validation errors
Preserve Pydantic error categories and diagnostics while masking nested
SecretStr inputs and avoiding retained validator exception objects.

Support custom error codes and safe pickling for Celery.
2026-11-05 20:18:34 +01:00
Florent Chehab 57a4446710 🔒️(summary) redact signed URLs in source logs and analytics
Strip URL credentials, query strings and fragments from file-service
logs and analytics source metadata using one shared helper.

Cover malformed URLs, non-string inputs and signed source URLs.
2026-11-05 20:18:34 +01:00
briquet df723e6a0e 🔒️(agents) upgrade python to 3.14.10 to fix CVE-2026-109518
The python:3.14.8-slim base image ships apt 3.0.3, which is affected by
CVE-2026-109518.
2026-11-05 18:30:51 +01:00
briquet 900ebd3edd 👷(ci) ignore zlib CVE-2026-76844 in trivy
Debian hasn't provided a fix yet
2026-11-05 18:30:51 +01:00
briquet c4fbfdd302 🔒️(backend) refuse unregistered rooms whose slug is a UUID
A registered room's LiveKit room is named after its id, and a room slug
can never be a UUID. A lookup such as "<id>!" does not parse as a UUID,
so it was searched as a slug, found nothing and was treated as an
unregistered room. Its slug, normalized back to the bare id, then got a
token for the registered room without going through the lobby, whenever
unregistered rooms are allowed.

Unregistered rooms whose slug is a UUID now return a 404.
2026-11-05 18:30:51 +01:00
briquet 6f1b9ddba7 🐛(frontend) show join errors instead of silently waiting in the lobby
When joining failed with anything but a 404/410, the lobby started
waiting as if entry had to be requested. Requesting entry now fails
uniformly.
2026-11-05 18:30:51 +01:00
briquet e412dec76f 🔥(backend) drop is_administrable from unregistered room payloads
Registered rooms stopped returning is_administrable in v1.25.0, and
nothing reads it for unregistered rooms either: administrable rooms are
detected from their accesses.

The frontend ApiRoom type drops is_administrable as well, which the API
no longer returns.
2026-11-05 18:30:51 +01:00
briquet 6af43ebf1d 🔒️(frontend) send the display name in the join request body
The room query joins the room, creating it first when missing, and keeps
the response, LiveKit credentials included, in the room cache entry. It
joins again to renew the token once its data is 6 hours old.

Conference and Lobby component were simplified by regrouping custom
hooks, states/refs, memo/callback and mutations.
2026-11-05 18:30:51 +01:00
briquet 4e953da226 🔒️(backend) issue room access tokens via a POST join endpoint
Room access tokens were returned by GET /rooms/{id}/, with the LiveKit
display name passed as a query parameter. We want to leverage Django's CSRF
checks and avoid displaying usernames in access logs.

Tokens are now only minted by POST /rooms/{id}/join/, which reads the
display name from the request body and the livekit information have been
removed where they were not required.

Joining an unregistered room now builds its livekit block with
generate_livekit_config, which mints the same token as the dict it
replaces.

The serializer and the lobby now share LobbyService.can_bypass_lobby to
decide who may enter directly to centralize the logic.
2026-11-05 18:30:51 +01:00
leo f45628d881 ⬆️(dependencies) update python dependencies in summary
Update python dependencies in summary.

Co-Authored-By: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-11-03 20:19:34 +01:00
leo 6fb698a63d ⬆️(dependencies) update python dependencies in agents
Update python dependencies in agents.

Co-Authored-By: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-11-03 20:19:34 +01:00
leo 5887795858 ⬆️(dependencies) update python dependencies in backend
Update python dependencies in backend.

Co-Authored-By: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-11-03 20:19:34 +01:00
leo 21b9b42cb1 👷(ci) update trivyignore
The trivy scan fails on HIGH vulnerability CVE-2026-119373
found in the Debian 13 base images. As it has no fixed version,
add an exception to trivyignore. Also add expiry date for all
exceptions.
2026-11-03 19:41:26 +01:00
briquet 2b9eba72a6 ⬆️(dev) upgrade LiveKit server to v1.13.11 and egress to v1.15.0
Upgrade the local and helm dev stacks
2026-11-03 08:43:25 +01:00
briquet 327f405559 🐛(frontend) show the transcript mic hint when every mic is muted
Since LiveKit Egress v1.13, transcripts no longer go through Chrome:
audio-only egresses always use the SDK source, which only starts on the
first audio packet. A muted microphone sends none, so a transcript
started while every microphone is muted stays "starting" until someone
unmutes.

The hint asking participants to turn on their microphone only showed
when no microphone was published at all, as it counted muted ones. Only
count unmuted microphones for transcripts. Screen recordings still run
in Chrome and keep counting every track.
2026-11-03 08:43:25 +01:00
briquet 35c3491ab2 ✨(frontend) tell participants when a recording is interrupted
The backend now notifies participants with transcriptionInterrupted or
screenRecordingInterrupted when LiveKit Egress cuts a recording short but
keeps what it recorded so far.
2026-11-03 08:43:25 +01:00
briquet 41f3d1def4 ✨(backend) keep recordings cut short by LiveKit egress
Since LiveKit Egress v1.14, an egress cut short still uploads what it
recorded, but Meet either throws it away or saves it silently:
- a transcript that loses its room connection, once the SDK gave up
  reconnecting, ends FAILED with "recording ended early: connection to
  room failed": it was flagged as failed and was never transcribed
- an egress drained because its host ran out of CPU ends COMPLETE with
  the end reason "CPU exhausted": the truncated file was saved without
  telling anyone. If the drain outlasts its grace period, the egress gets
  killed and ends FAILED instead, with the same end reason, and with or
  without its file uploaded.

A new INTERRUPTED worker event is created :
- the recording is saved and sent to external services as usual, and the
participants of an active recording are told that only part of it was
kept.
- A FAILED egress only counts as interrupted when one of its files
has a location, which egress sets once the upload succeeded. Other
failures stay failures, even with an uploaded file.
2026-11-03 08:43:25 +01:00
briquet 22a93947d5 ✨(frontend) add an exponential backoff reconnection policy
When a LiveKit node crashes or Redis stalls, every client loses its signal
connection at the same time. LiveKit's default policy has been jittered
since 2.23.0, but it retries within 500 ms, then again within 450 ms, and
stops after about 45 s of delays, which can be too short for an outage.

Add a reconnection policy with a random exponential backoff. Before its
first attempt, a client waits a random delay up to 1 s by default. That
ceiling doubles after every failed attempt, capped at 10 s, and later
delays fall between half the ceiling and the ceiling. Without that floor,
retries happen at the same time when the service comes back

A client gives up after 3 minutes by default, unless its media still
flows. A client that drops again soon after reconnecting resumes the
backoff one step above where it stopped. The PostHog telemetry now reports how
long a reconnection lasted and when the policy gave up.
2026-11-03 08:43:25 +01:00
briquet b9748983ac ✨(backend) expose livekit reconnect settings in the config
When a LiveKit node crashes or Redis stalls, every client loses its signal
connection at the same time and tries to reconnect at once. The frontend
is about to spread these reconnections with a random exponential backoff.

Expose its parameters under livekit.reconnect in the config endpoint to
let each deployment tune them without rebuilding the frontend.

LIVEKIT_RECONNECT_MAX_DELAY_MS defaults to 10 s: with delays drawn between
half of it and all of it, a client retries every 7.5 s on average, close
to the 7 s of LiveKit's default policy in a long outage.

LIVEKIT_RECONNECT_BACKOFF_ENABLED switches back to LiveKit's default
policy.
2026-11-03 08:43:25 +01:00
briquet f7c3f392ef 🐛(frontend) show a page when the connection ends unexpectedly
When a room ended for a reason Conference did not handle, the user was
left on a dead call screen. This happens when the SDK gives up
reconnecting and when the join itself fails. Since livekit-client 2.22.1
it also happens when the WebSocket upgrade times out during the join,
which 2.21.0 reported as a cancellation and sent to the "you left the
meeting" page.

Send these users to the feedback page with a heading of its own: the
connection was lost if the room had connected, the meeting could not be
joined otherwise. Both keep the buttons to rejoin or go home. A join that
never succeeded leaves no call to rate, so the rating is hidden.

A full reconnection removes every remote participant until it succeeds,
which the idle-disconnect timer took for being alone in the room. Only
run that timer while the room is connected, so a long reconnection does
not end with the user sent away.
2026-11-03 08:43:25 +01:00
briquet 0c357c8b46 ⬆️(frontend) upgrade livekit-client from 2.21.0 to 2.23.0 2026-11-03 08:43:25 +01:00
briquet cae6c19da8 🔖(minor) bump release to 1.37.0 2026-11-01 14:07:10 +01:00
briquet 455daf1e8f ⚡️(docker) build frontend assets natively for arm64 images
Release images were building the JS bundles under QEMU for arm64, which
took most of the 10 to 17 minutes spent on each frontend image on CI.

Since the output is mostly static files, we can use the build platform
for the multi stage build steps of frontend images and share them
between architectures.
2026-10-31 11:41:06 +01:00
briquet 73b182b34a ⬆️(backend) upgrade django-lasuite to v1.0.0
Upgrade django-lasuite from 0.0.29 to 1.0.0.

An inactive token on the external API is now refused with a 401 error
instead of a 400.
2026-10-31 11:22:28 +01:00
briquet 6a1622cd50 🔒️(frontend) keep rooms out of search engine
Room URLs were appearing in search engine results.
The indexing is now restricted  to allow only the landing page.

- Emit a separate `home.html` during the Vite build without a `noindex` tag
- Configure Nginx to serve `home.html` exclusively at `/`
- Serve all other routes with a `noindex` meta tag and an
  `X-Robots-Tag: noindex` header
2026-10-31 11:22:28 +01:00
briquet 56b5a34ec5 ✨(frontend) replace the Crisp chat with the La Suite support form
LaSuite no longer uses Crisp for support.

Users now reach the team through the La Suite support form. It opens
from the in-room "Contact support" menu item and from the main page
as well

The widget is configured with the widget_script_url, widget_api_url and
widget_channel keys that uses shared values with other LaSuite projects
It won't show if the script URL are not configured.
2026-10-29 17:01:06 +01:00
Florent Chehab 815baa73e8 ⬆️(summary) update ffmpeg to 8.1.3-r0
8.1.2 is not available anymore and the image build fails.
2026-10-26 13:28:56 +01:00
briquet 85670a02d5 🔖(patch) bump release to 1.36.3 2026-10-23 21:48:52 +02:00
briquet 21121e8bc1 🔒️(backend) upgrade djangorestframework to 3.18.2
Fix three vulnerabilities in djangorestframework

- CVE-2026-114136: a compressing codec in the Content-Type charset
  lets an unauthenticated body bypass Django's size limit,
  causing memory-amplification denial of service
- CVE-2026-114137: TokenProxy.delete() reports success without deleting
  the token
- CVE-2026-114138: crafted Accept headers make content negotiation burn
  seconds of CPU per request, exhausting workers
2026-10-23 21:46:44 +02:00
briquet 1717c65f2b 🔒️(frontend) upgrade nginx to 1.30.6 to fix CVE-2026-15176
Bump the nginx-unprivileged base image to 1.30.6-alpine3.24, which
fixes a heap overflow when using alias with rewrite, along with
CVE-2026-93479, CVE-2026-95101 and CVE-2026-97629.

https://nginx.org/en/security_advisories.html
2026-10-23 21:46:44 +02:00
briquet fa20d52248 🔒️(all) upgrade brotli to fix CVE-2026-111497
CVE-2026-111497 is an out-of-bounds write in the brotli decoder,
reachable by decompressing crafted brotli data.

- backend: upgrade Brotli to 1.2.1, used by urllib3 and aiohttp to
  decode "Content-Encoding: br" responses
- docker: require brotli-libs >= 1.2.1-r0 in the backend, summary and
  frontend images, the nginx base image ships 1.2.0-r1
- frontend: build the Outlook add-in on Node 22.23.4, Node 20 is EOL
  and will not get the fix

https://www.strix.ai/cve/CVE-2026-111497
2026-10-23 21:46:44 +02:00
briquet 75df71733a 🗃️(backend) add missing editable=False on client_secret_sha256
Migration 0025 declared client_secret_sha256 without editable=False,
while the model field is setting it.
2026-10-21 23:19:05 +02:00
briquet bae6affcba 🔖(minor) bump release to 1.36.2 2026-10-21 21:59:23 +02:00
briquet 708f0d332e 🔒️(frontend) upgrade Node to 22.23.4 and 24.22.1
Pin Node images to the 2026-10-20 security releases
2026-10-21 21:58:23 +02:00
199 changed files with 7997 additions and 1488 deletions
+17 -8
View File
@@ -2,19 +2,28 @@
# Review regularly and remove entries once Debian ships a fix.
# util-linux
CVE-2026-76642
CVE-2026-78408
CVE-2026-78409
CVE-2026-78410
CVE-2026-76642 exp:2026-12-05
CVE-2026-78408 exp:2026-12-05
CVE-2026-78409 exp:2026-12-05
CVE-2026-78410 exp:2026-12-05
# acl
CVE-2026-54369
CVE-2026-54369 exp:2026-12-05
# ncurses
CVE-2025-69720
CVE-2025-69720 exp:2026-12-05
# systemd
CVE-2026-16742
CVE-2026-16742 exp:2026-12-05
# perl-base (fix deferred by Debian)
CVE-2026-9538
CVE-2026-9538 exp:2026-12-05
# libsqlite3-0 (fixed in sid 3.54.1-1; trixie marked no-dsa / minor)
CVE-2026-119373 exp:2026-12-05
CVE-2026-119374 exp:2026-12-05
CVE-2026-119379 exp:2026-12-05
# zlib (fixed in sid 1:1.3.dfsg+really1.3.2-4; trixie still vulnerable)
CVE-2026-76844 exp:2026-12-05
CVE-2026-85091 exp:2026-12-05
+1 -1
View File
@@ -215,7 +215,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: "24"
node-version: "24.22.1"
- name: Install dependencies
run: cd src/frontend/ && npm ci --ignore-scripts
+75 -3
View File
@@ -8,10 +8,82 @@ and this project adheres to
## [Unreleased]
## [1.36.1] - 2026-10-21
### Added
- 🌐(i18n) add Portuguese language support
- ✨(frontend) add jittered exponential backoff to LiveKit reconnections
- ✨(frontend) tell participants when a recording is interrupted
### Changed
- ⬆️(frontend) upgrade livekit-client to 2.23.0
- ✨(backend) expose LiveKit reconnection settings in the config
- ✨(backend) keep recordings cut short by LiveKit egress
- ⬆️(dev) upgrade LiveKit server to v1.13.11 and egress to v1.15.0
- ⬆️(backend) update python dependencies
- ⬆️(agents) update python dependencies
- ⬆️(summary) update python dependencies
- 🔒️(backend) issue room access tokens via POST rooms/{id}/join/ only
- 🔒️(frontend) send the display name in the join request body
- 🔥(backend) drop is_administrable from unregistered room payloads
- 🍱(frontend) add Portuguese glyphs to Marianne subsetted fonts
- 🚸(frontend) bring new speakers to the top of the carousel
- 🚸(frontend) bring raised hands into view in the carousel
- ⚡️(frontend) serve illustrations as WebP with a PNG fallback
### Fixed
- 📌(summary) pin kombu to keep celery workers compatible with redis 5
- 🐛(frontend) show a page when the connection ends unexpectedly
- 🐛(frontend) show the transcript mic hint when every mic is muted
- 🐛(frontend) show join errors instead of silently waiting in the lobby
- 🐛(frontend) send users who cannot enter directly back to the lobby
- 🔒️(backend) refuse unregistered rooms whose slug is a UUID
- 🔒️(agents) upgrade python to 3.14.10 to fix CVE-2026-109518
- 🔒️(docker) upgrade musl-utils to 1.2.6-r3 to fix CVE-2026-107891
- ♿️(frontend) close the invite dialog with the Escape key #1916
- 🔒️(docker) move frontend to nginx/nginx-unprivileged image
- 🐛(frontend) accept meeting links with query params in join dialog
- 🐛(backend) allow muting when everyone_can_mute is stored as null #1942
- 🐛(frontend) apply the sans font token on every page
- 🐛(frontend) promote the active speaker in the carousel layout
## [1.37.0] - 2026-11-01
### Changed
- ⬆️(summary) update ffmpeg to 8.1.3-r0
- ✨(frontend) replace the Crisp chat with the La Suite support form
- 🔒️(frontend) keep rooms out of search engine and AI indexes
- ⬆️(backend) upgrade django-lasuite to v1.0.0
- ⚡️(docker) build frontend assets natively for arm64 images
### Fixed
- 🔒️(summary) protect signed URLs in models, source logs and analytics metadata
- 🔒️(summary) mask secret validation inputs and sanitize upstream and task errors
- 🔒️(summary) scrub sensitive Sentry event and breadcrumb content while retaining
redacted frame locals and application DEBUG diagnostics
## [1.36.3] - 2026-10-23
### Fixed
- 🔒️(backend) upgrade brotli to 1.2.1 to fix CVE-2026-111497
- 🔒️(frontend) upgrade nginx to 1.30.6 to fix CVE-2026-15176
- 🔒️(docker) upgrade brotli-libs to 1.2.1 to fix CVE-2026-111497
- 🔒️(frontend) build the Outlook add-in on Node 22.23.4
- 🔒️(backend) upgrade djangorestframework to 3.18.2 to fix CVE-2026-114136,
CVE-2026-114137 and CVE-2026-114138
## [1.36.2] - 2026-10-21
### Fixed
- 🔒️(frontend) upgrade Node to 22.23.4 and 24.22.1 security releases
## [1.36.1] - 2026-10-21
### Changed
- 🔖(helm) release chart 0.0.28
@@ -163,7 +235,7 @@ and this project adheres to
### Fixed
- 🐛(helm) probe liveness on __lbheartbeat__ and readiness on __heartbeat__
- 🐛(helm) probe liveness on `__lbheartbeat__` and readiness on `__heartbeat__`
- 🐛(helm) render periodSeconds and failureThreshold on probes
- 🐛(backend) report the app release to Sentry instead of "NA"
- 🐛(frontend) play the waiting room notification sound on every arrival
+5 -3
View File
@@ -9,7 +9,8 @@ RUN python -m pip install --upgrade pip
# Upgrade system packages to install security updates
RUN apk update && \
apk upgrade && \
apk add --no-cache --upgrade 'ca-certificates>=20261020-r0' && \
apk add --no-cache --upgrade 'ca-certificates>=20261020-r0' \
'musl-utils>=1.2.6-r3' && \
update-ca-certificates
# ---- Back-end builder image ----
@@ -39,7 +40,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# ---- mails ----
FROM node:22-alpine AS mail-builder
FROM node:22.23.4-alpine AS mail-builder
RUN apk upgrade --no-cache ca-certificates
@@ -87,7 +88,8 @@ RUN apk --no-cache add \
libffi-dev \
pango \
libmagic \
shared-mime-info
shared-mime-info \
'brotli-libs>=1.2.1-r0'
# Copy entrypoint
+53
View File
@@ -16,6 +16,59 @@ the following command inside your docker container:
## [Unreleased]
### Room access tokens moved to POST /join/
LiveKit access tokens are no longer returned when reading a room. They are henceforth issued by a dedicated `POST
/api/v1.0/rooms/{id}/join/` endpoint, which receives the display name in its JSON body. Display names therefore no
longer appear in URLs and token retrieval by logged-in users is now protected against CSRF.
- `GET /api/v1.0/rooms/{id}/` no longer returns the `livekit` block, and neither do the list, create and update
responses
- The `username` field of `POST /api/v1.0/rooms/{id}/request-entry/` is now limited to 255 characters
- When the caller may enter the room directly, the response contains the room data plus a `livekit` block
- When the caller must go through the lobby, the response has no `livekit` block
- Session-authenticated callers must send the CSRF token (`X-CSRFToken` header and `csrftoken` cookie)
- Every join returning a `livekit` block mints a new token, valid for 6 hours, and anonymous callers get a new
participant identity each time.
- Joining again returns no `livekit` block to callers admitted through the lobby.
- Unregistered rooms are always entered directly: joining one always returns a `livekit` block.
- Unregistered rooms are no longer described with an `is_administrable` field,
### Unregistered rooms can no longer be named after a UUID
When `ALLOW_UNREGISTERED_ROOMS` is enabled, reading or joining a room whose identifier normalizes to an empty slug or to
a UUID now returns a 404 instead of describing or letting callers join the unregistered room.
This closes a lobby bypass since a registered LiveKit room is named after its room id. An identifier crafted to not
parse as a UUID, yet to normalize to an existing room's id, matched no registered room and was treated as an
unregistered one. The token minted for that unregistered room was valid for the registered room: anyone could join a
trusted or restricted room without being admitted through the lobby.
## v1.37.0
### Crisp support chat replaced by the La Suite support form
The Crisp chat is gone, the support button, now open the La Suite support form. They are hidden unless these `FRONTEND_SUPPORT` keys are all set.
The former help links for recording/transcription are now redirecting to the adhoc page in the official documentation
### Pages hidden from search engines
Every page except `/` now carries a `noindex` tag and, with the provided nginx configurations, an `X-Robots-Tag` header to stay out of search indexes.
A new `robots.txt` also blocks the main AI crawlers. If you maintain your own nginx configuration, copy the `location = /` and `location = /home.html` blocks from `src/frontend/default.conf`, and the `X-Robots-Tag` header from its `location /`.
### LiveKit server v1.13.11 and egress v1.15.0
The development stack now runs LiveKit server v1.13.11 and LiveKit Egress v1.15.0. When upgrading your own deployment:
- Deploy this release of Meet first, then upgrade egress, then the server. Older Meet releases discard the partial recordings described below.
- Transcripts no longer go through Chrome. Since egress v1.13, audio-only recordings always use the egress SDK pipeline.
- A transcript now waits for actual audio: it stays "starting", and participants are asked to turn on their microphone, while every microphone is muted.
- Recordings cut short are now kept. Since egress v1.14, a transcript that lost the room, or any recording drained because its egress host ran out of CPU, still uploads what it recorded, even though egress may report it as failed. Meet saves and processes it as usual, and tells participants the recording was interrupted. A screen recording that loses the room is still reported as complete, and saved without a warning.
- Telephony: since server v1.13.8, a call that matches no dispatch rule, for example while no room is active, is rejected with a SIP 404 instead of being silently dropped.
## [v1.35.0]
### Purging deleted rooms
Deleting a room now only soft deletes it: it is hidden but keeps its slug and PIN code. The `purge_inactive_rooms` command also permanently deletes soft-deleted rooms. See [the room purge documentation](docs/features/room-purge.md).
+3 -3
View File
@@ -175,7 +175,7 @@ services:
working_dir: /app
node:
image: node:22-alpine
image: node:22.23.4-alpine
user: "${DOCKER_USER:-1000}"
environment:
HOME: /tmp
@@ -218,7 +218,7 @@ services:
- kc_postgresql
livekit:
image: livekit/livekit-server:v1.13.6
image: livekit/livekit-server:v1.13.11
entrypoint: /livekit-server --dev --bind 0.0.0.0 --config ./config.yaml
ports:
- "7880:7880"
@@ -233,7 +233,7 @@ services:
- livekit-egress
livekit-egress:
image: livekit/egress:v1.11.0
image: livekit/egress:v1.15.0
environment:
EGRESS_CONFIG_FILE: ./livekit-egress.yaml
volumes:
+13 -1
View File
@@ -33,11 +33,22 @@ server {
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|webp|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
location = / {
try_files /home.html /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
location = /home.html {
internal;
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
@@ -45,6 +56,7 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
add_header X-Robots-Tag "noindex, nofollow" always;
}
# Optionally, handle 404 errors by redirecting to index.html
+5 -9
View File
@@ -1,5 +1,5 @@
# ---- Front-end image ----
FROM node:22-alpine AS frontend-deps
FROM --platform=$BUILDPLATFORM node:22.23.4-alpine AS frontend-deps
RUN apk upgrade --no-cache ca-certificates
@@ -41,7 +41,7 @@ COPY ./docker/dinum-frontend/fonts/ \
./dist/assets/fonts/
# ---- Addons builder image ----
FROM node:20-alpine AS addons-builder
FROM --platform=$BUILDPLATFORM node:22.23.4-alpine AS addons-builder
RUN apk upgrade --no-cache ca-certificates
@@ -58,15 +58,11 @@ RUN npx webpack --mode production
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.30.5-alpine3.24 AS frontend-production
FROM nginx/nginx-unprivileged:1.30.6-alpine3.24 AS frontend-production
USER root
RUN apk upgrade --no-cache libexpat && \
apk add --no-cache --upgrade 'pcre2>=10.49-r0' 'tiff>=4.7.2-r0' \
'libx11>=1.8.14-r0' 'ca-certificates>=20261020-r0' && \
apk del curl
USER nginx
RUN apk del curl && \
rm -f /usr/share/nginx/html/50x.html
USER nginx
# Un-privileged user running the application
+14 -1
View File
@@ -43,6 +43,7 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache" always;
add_header Expires 0 always;
add_header X-Robots-Tag "noindex, nofollow" always;
set $ms_domains "https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com";
@@ -71,11 +72,22 @@ server {
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|webp|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
location = / {
try_files /home.html /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
location = /home.html {
internal;
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
@@ -83,6 +95,7 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
add_header X-Robots-Tag "noindex, nofollow" always;
}
# Optionally, handle 404 errors by redirecting to index.html
+1 -1
View File
@@ -1,4 +1,4 @@
FROM livekit/livekit-server:v1.13.6
FROM livekit/livekit-server:v1.13.11
# We inject the nip.io certificate manually because the livekit chart doesn't support volume mounting
COPY rootCA.pem /etc/ssl/certs/
+2 -1
View File
@@ -1,4 +1,5 @@
log_level: debug
logging:
level: debug
api_key: devkey
api_secret: secret
ws_url: "ws://livekit:7880"
+1 -1
View File
@@ -37,7 +37,7 @@ $ make help
---
## Need Help?
If you need any assistance or have questions while getting started, feel free to reach out to @lebaudantoine anytime! Antoine is available to help you onboard and guide you through the process. Chat with him @antoine.lebaud:matrix.org, or from the [support hotline](https://go.crisp.chat/chat/embed/?website_id=58ea6697-8eba-4492-bc59-ad6562585041).
If you need any assistance or have questions while getting started, feel free to reach out to @lebaudantoine anytime! Antoine is available to help you onboard and guide you through the process. Chat with him @antoine.lebaud:matrix.org, or by email at [support-visio@numerique.gouv.fr](mailto:support-visio@numerique.gouv.fr).
---
+13 -4
View File
@@ -11,14 +11,23 @@ We do not cover internal signaling behavior. For full reference, see the [LiveKi
> [!IMPORTANT]
> The WebSocket is the backbone of LiveKit signaling. All signaling messages rely on it, and without it, ICE candidate exchange and peer connection setup cannot occur. If the WebSocket connection is lost, the client automatically attempts to resume the RTC session once connectivity is restored.
## Reconnection
When the WebSocket drops, for instance after a LiveKit node crash or during a Redis outage, every client in every room loses it at the same time. To keep them from all reconnecting at once, each client waits a random delay before every attempt. The first delay is at most `LIVEKIT_RECONNECT_BASE_DELAY_MS`. That ceiling then doubles after each failed attempt, capped at `LIVEKIT_RECONNECT_MAX_DELAY_MS`, and each later delay falls between half the ceiling and the ceiling. The first attempt always starts within 5 seconds of the drop.
A client gives up once its reconnection has lasted longer than `LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS`, and then shows a "connection lost" page. While only signaling is down and its media still flows, it keeps trying instead. A client that loses the connection again less than `LIVEKIT_RECONNECT_BACKOFF_RESET_MS` after reconnecting resumes the backoff one step above where it stopped.
## Environment Variables
| Variable | Type | Default | Purpose |
| ----------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LIVEKIT_FORCE_WSS_PROTOCOL` | Boolean | `True` | Forces the WebSocket URL to use `wss://`. Required for legacy browsers (Firefox <124, Chrome <125, Edge <125) where HTTPS URLs in `WebSocket()` may fail. |
| `LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND` | Boolean | `True` | Workaround for Firefox clients behind proxies that fail to establish WebSocket connections. Pre-establishes a dummy connection to “prime” the WebSocket. |
| Variable | Type | Default | Purpose |
| ----------------------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LIVEKIT_FORCE_WSS_PROTOCOL` | Boolean | `False` | Forces the WebSocket URL to use `wss://`. Required for legacy browsers (Firefox <124, Chrome <125, Edge <125) where HTTPS URLs in `WebSocket()` may fail. |
| `LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND` | Boolean | `False` | Workaround for Firefox clients behind proxies that fail to establish WebSocket connections. Pre-establishes a dummy connection to “prime” the WebSocket. |
| `LIVEKIT_RECONNECT_BACKOFF_ENABLED` | Boolean | `True` | Enables the reconnection backoff described above. When `False`, clients use LiveKit's default reconnection policy. |
| `LIVEKIT_RECONNECT_BASE_DELAY_MS` | Integer | `1000` | Longest wait before the first reconnection attempt, in milliseconds. It doubles after each failed attempt. Must be greater than 0. |
| `LIVEKIT_RECONNECT_MAX_DELAY_MS` | Integer | `10000` | Longest wait between two reconnection attempts, in milliseconds. Must be greater than 0. |
| `LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS` | Integer | `180000` | How long a client keeps trying to reconnect, in milliseconds. Not applied while its media still flows. Must be greater than 0. |
| `LIVEKIT_RECONNECT_BACKOFF_RESET_MS` | Integer | `120000` | How long a connection must stay up, in milliseconds, for the next reconnection to start from the shortest delay again. With `0`, every reconnection does. |
> [!NOTE]
> Questions? Open an issue on [GitHub](https://github.com/suitenumerique/meet/issues/new?assignees=&labels=bug&template=Bug_report.md) or join our [Matrix community](https://matrix.to/#/#meet-official:matrix.org).
+6 -1
View File
@@ -348,7 +348,7 @@ These are the environmental options available on meet backend.
| CSRF_TRUSTED_ORIGINS | CSRF trusted origins list | [] |
| FRONTEND_CUSTOM_CSS_URL | URL of an additional CSS file to load in the frontend app. If set, a `<link>` tag with this URL as href is added to the `<head>` of the frontend app | |
| FRONTEND_ANALYTICS | Analytics information | {} |
| FRONTEND_SUPPORT | Crisp frontend support configuration, also you can pass help articles, with `help_article_transcript`, `help_article_recording`, `help_article_more_tools` | {} |
| FRONTEND_SUPPORT | La Suite support form (`widget_script_url`, `widget_api_url`, `widget_channel`) and help articles (`help_article_*`) | {} |
| FRONTEND_MANIFEST_LINK | Link to the "Learn more" button on the homepage | {} |
| FRONTEND_SILENCE_LIVEKIT_DEBUG | Silence LiveKit debug logs | false |
| FRONTEND_IS_SILENT_LOGIN_ENABLED | Enable silent login feature | true |
@@ -413,6 +413,11 @@ These are the environmental options available on meet backend.
| LIVEKIT_API_TIMEOUT_SECONDS | Seconds before a call to the LiveKit API gives up | 10 |
| LIVEKIT_FORCE_WSS_PROTOCOL | Enables WSS protocol conversion for legacy browser compatibility (Firefox <124, Chrome <125, Edge <125) where HTTPS URLs fail in WebSocket() constructor. | false |
| LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND | Firefox-only connection warmup: pre-calls WebSocket endpoint (expecting 401) to initialize cache, resolving proxy/network connectivity issues. | false |
| LIVEKIT_RECONNECT_BACKOFF_ENABLED | Spread client reconnections with a random exponential backoff. When false, clients use LiveKit's default reconnection policy | true |
| LIVEKIT_RECONNECT_BASE_DELAY_MS | Longest wait before the first reconnection attempt, in milliseconds. It doubles after each failed attempt. Must be greater than 0 | 1000 |
| LIVEKIT_RECONNECT_MAX_DELAY_MS | Longest wait between two reconnection attempts, in milliseconds. Must be greater than 0 | 10000 |
| LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS | How long a client keeps trying to reconnect, in milliseconds. Not applied while its media still flows. Must be greater than 0 | 180000 |
| LIVEKIT_RECONNECT_BACKOFF_RESET_MS | How long a connection must stay up, in milliseconds, for the next reconnection to start from the shortest delay again. With 0, every reconnection does | 120000 |
| RESOURCE_DEFAULT_ACCESS_LEVEL | Default resource access level for rooms | public |
| ALLOW_UNREGISTERED_ROOMS | Allow usage of unregistered rooms | true |
| ROOM_INACTIVITY_DELETION_DAYS | Days without being started after which a room is purged. Unset to never purge | |
@@ -2,6 +2,7 @@
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex, nofollow" />
<title data-app-name></title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<script nonce="NONCE_PLACEHOLDER" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title data-app-name></title>
<link rel="stylesheet" href="../styles/spinner.css" />
<script nonce="NONCE_PLACEHOLDER" src="/addons/outlook/config.js"></script>
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title data-app-name></title>
<link rel="stylesheet" href="taskpane.css" />
<script nonce="NONCE_PLACEHOLDER" src="/addons/outlook/config.js"></script>
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title data-app-name></title>
<link rel="stylesheet" href="../styles/spinner.css" />
<script nonce="NONCE_PLACEHOLDER" src="/addons/outlook/config.js"></script>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.14.8-slim AS base
FROM python:3.14.10-slim AS base
# Install system dependencies required by LiveKit, fetching packages over HTTPS only for Bureautix proxy
RUN sed -i "s|^URIs: http://|URIs: https://|" /etc/apt/sources.list.d/debian.sources \
+10 -10
View File
@@ -1,24 +1,24 @@
[project]
name = "agents"
version = "1.36.1"
version = "1.37.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.7.0",
"livekit-plugins-deepgram==1.7.0",
"livekit-plugins-silero==1.7.0",
"livekit-agents==1.8.9",
"livekit-plugins-deepgram==1.8.9",
"livekit-plugins-silero==1.8.9",
"livekit-plugins-kyutai-lasuite==0.0.6",
"boto3==1.43.56",
"python-dotenv==1.2.3",
"protobuf==7.36.0",
"sentry-sdk==2.68.1",
"websockets==17.1",
"boto3==1.43.118",
"python-dotenv==1.2.4",
"protobuf==7.36.2",
"sentry-sdk==2.73.0",
"websockets==17.3",
"httpx==0.28.1",
]
[project.optional-dependencies]
dev = [
"ruff==0.16.4",
"ruff==0.17.2",
]
[tool.uv]
+306 -242
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 5
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.14'",
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "agents"
version = "1.36.1"
version = "1.37.0"
source = { virtual = "." }
dependencies = [
{ name = "boto3" },
@@ -31,17 +31,17 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "boto3", specifier = "==1.43.56" },
{ name = "boto3", specifier = "==1.43.118" },
{ name = "httpx", specifier = "==0.28.1" },
{ name = "livekit-agents", specifier = "==1.7.0" },
{ name = "livekit-plugins-deepgram", specifier = "==1.7.0" },
{ name = "livekit-agents", specifier = "==1.8.9" },
{ name = "livekit-plugins-deepgram", specifier = "==1.8.9" },
{ name = "livekit-plugins-kyutai-lasuite", specifier = "==0.0.6" },
{ name = "livekit-plugins-silero", specifier = "==1.7.0" },
{ name = "protobuf", specifier = "==7.36.0" },
{ name = "python-dotenv", specifier = "==1.2.3" },
{ name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.4" },
{ name = "sentry-sdk", specifier = "==2.68.1" },
{ name = "websockets", specifier = "==17.1" },
{ name = "livekit-plugins-silero", specifier = "==1.8.9" },
{ name = "protobuf", specifier = "==7.36.2" },
{ name = "python-dotenv", specifier = "==1.2.4" },
{ name = "ruff", marker = "extra == 'dev'", specifier = "==0.17.2" },
{ name = "sentry-sdk", specifier = "==2.73.0" },
{ name = "websockets", specifier = "==17.3" },
]
provides-extras = ["dev"]
@@ -286,30 +286,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.43.56"
version = "1.43.118"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/53/05/23e1aa8c9e4b0399a61e7fd65c4f9cc0625121f24760e37471f776404abb/boto3-1.43.56.tar.gz", hash = "sha256:57c90df9fb026f2e6ae22530861198130203733c5c9ec4e5cca3a4037f5a8db4", size = 112673, upload-time = "2026-07-24T19:31:48.606Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/8b/5694c8433b5202f34a82def4edee274f7df1eba2919c66d9d389d2202ec8/boto3-1.43.118.tar.gz", hash = "sha256:c0620f799e0f0c222ba42c337b65cdbcd3da38a3121187f634d8926cbbb28a9c", size = 112649, upload-time = "2026-10-22T19:22:09.629Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/57/3a960c9f581c00f2a591901b46e035ff79ab3956d16607f12306b3b8d483/boto3-1.43.56-py3-none-any.whl", hash = "sha256:feb699d4ab241ef5c1b80bb58277be2aaad365cd4b672d7817e0bc59ee45131b", size = 140026, upload-time = "2026-07-24T19:31:47.155Z" },
{ url = "https://files.pythonhosted.org/packages/bb/a3/65bb3d64928f3c2da17640b92044084150317f02adf22442cb0c52af2a8b/boto3-1.43.118-py3-none-any.whl", hash = "sha256:ab8f3d2c622ef039338b4f228d97ea55f1abd573d84bb9b9a5ee8cb6780c0ccd", size = 140045, upload-time = "2026-10-22T19:22:08.353Z" },
]
[[package]]
name = "botocore"
version = "1.43.104"
version = "1.43.123"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/47/5825401cb9883bc0fbf981dfb4893371f48bc77527bde6c1efe8f9aa8754/botocore-1.43.104.tar.gz", hash = "sha256:099f84876df9f6dc23a24b4d8c02da2d73c6949249761dd85585f735b825c182", size = 16254091, upload-time = "2026-09-28T19:32:10.964Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/de/011fc2a47fb066c47064b122498d2fd21f55bc90ea514acbddc37ca0f8c1/botocore-1.43.123.tar.gz", hash = "sha256:80948cb9b1cdf1a1a25809089a42e3a1bc4af0258d198222db6e97dcc660dd0c", size = 16486269, upload-time = "2026-10-30T19:18:44.609Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/e9/88407daa4bedcb7c6e01b6373c2f78374775ed12557aa768c6d88f2fb78d/botocore-1.43.104-py3-none-any.whl", hash = "sha256:a447978a815f8ac7ef2f4ba103b6a0c310e56536386974df769edb1326f3b208", size = 15947058, upload-time = "2026-09-28T19:32:08.243Z" },
{ url = "https://files.pythonhosted.org/packages/db/96/4f2dffd304425c07160ab2c12cb13322d852fd956fe9d4386228c93f02ea/botocore-1.43.123-py3-none-any.whl", hash = "sha256:e30580f9c1282c321529238ff8ff75066096a2adbd0a293c8ae592c21d1733a3", size = 16185987, upload-time = "2026-10-30T19:18:39.794Z" },
]
[[package]]
@@ -555,15 +555,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "docstring-parser"
version = "0.18.0"
@@ -764,6 +755,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpcore2"
version = "2.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/61/afa9d6b7452f9eca73f73d45324eb0bb52f098fd5e6049bf6b8263be9b4e/httpcore2-2.16.0.tar.gz", hash = "sha256:c72cd0413563b17e1cd8354677dff887466f8ec2551679759e93126534428643", size = 69886, upload-time = "2026-10-21T07:09:45.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
@@ -779,6 +783,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "httpx2"
version = "2.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", marker = "sys_platform != 'emscripten'" },
{ name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/73/5ca2cedf5e4e50423d1c258877865a6b62793633b561c58499f8aeb33b7e/httpx2-2.16.0.tar.gz", hash = "sha256:0d54c98128e8f2ff7fbbde0fca8c359e5f032edeaaaac443cdb7f31f685bcd7f", size = 103116, upload-time = "2026-10-21T07:09:46.348Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/3d/2e4bc4fd1ef9770871669adb348d612a6e4786b303a23f9935b81c48dd60/httpx2-2.16.0-py3-none-any.whl", hash = "sha256:fd2c5d7909e8d86fe92d53020c310ca0e4e431b838c207c5754b9bbf0308d49a", size = 97880, upload-time = "2026-10-21T07:09:44.01Z" },
]
[[package]]
name = "httpx2-jsfetch"
version = "1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
]
[[package]]
name = "idna"
version = "3.20"
@@ -894,16 +924,16 @@ wheels = [
[[package]]
name = "json-repair"
version = "0.60.1"
version = "0.63.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/a6/d69888cb4ffde30e80db1e6c32caaadd2f984a80067d5ea72c2cb3f61c3f/json_repair-0.60.1.tar.gz", hash = "sha256:841661cdd2df507c9a4e189097f38ca6bc372e06d4b4e36d72e590f68176c290", size = 49451, upload-time = "2026-06-03T17:28:44.451Z" }
sdist = { url = "https://files.pythonhosted.org/packages/72/29/653c36e0de80f7048f01298a9098f1c66bb9da162e92ad6a6e2c3202cc52/json_repair-0.63.5.tar.gz", hash = "sha256:75a6a1a4ea954935dd3fb69b6e49ac0e3e3d69b4290127a0c8d37af93b709f3a", size = 52945, upload-time = "2026-09-22T18:36:08.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/1f/2a2b5eea8ef5762a86ad3f8fddddaaba2c0d76dd44e644b9158900868bec/json_repair-0.60.1-py3-none-any.whl", hash = "sha256:ba6ff974f2a8bef2f7768144a7f03f870a816443f03da27a49cdd0ec31a78049", size = 48045, upload-time = "2026-06-03T17:28:43.038Z" },
{ url = "https://files.pythonhosted.org/packages/6b/a4/222e2581667c326183a87df229e772140fbf3e5dc03ec73e2a0e74804114/json_repair-0.63.5-py3-none-any.whl", hash = "sha256:0e39b3066f7829b4bd7fc614e463cfd4ff7a4c46f82f5b65431fc11e29a74c81", size = 51334, upload-time = "2026-09-22T18:36:07.05Z" },
]
[[package]]
name = "livekit"
version = "1.1.14"
version = "1.1.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiofiles" },
@@ -911,18 +941,18 @@ dependencies = [
{ name = "protobuf" },
{ name = "types-protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/5d/bfaf1cc73f960b40294f604d334f05e628b0a07de3c47e475d760996a8d0/livekit-1.1.14.tar.gz", hash = "sha256:47428e10ecf20d7db4ee9fde4009bf96578c003b1ae6e1c5e7e4837a55902393", size = 375000, upload-time = "2026-07-31T14:05:14.425Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/c7/9a666b4a218c1f308b701cb5cd523a49532e7280c6dcc5cab909b8a8adf4/livekit-1.1.21.tar.gz", hash = "sha256:c6c1783a99954147e46995087cbbfb77844049c0d53c33b761e0dfa7ff47256c", size = 400299, upload-time = "2026-10-16T03:59:11.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/ff/a2659522b3cf860b9b4453e1ec12d4b4c7e9cfd2b672f2cf925016d73492/livekit-1.1.14-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:5f671b1752c93b878cb241b84fd3f72a31f857c3927755d672cfb7656a84778c", size = 10196322, upload-time = "2026-07-31T14:05:04.167Z" },
{ url = "https://files.pythonhosted.org/packages/82/a2/89f32d369cc78cb1a50b2a9e635c653f88d86ea4338ccdfa7b2d4ca0aecd/livekit-1.1.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:efa16b9036b0b592e5399fdb858c1f04ec8a32c385184c705f030952f72174e8", size = 9019745, upload-time = "2026-07-31T14:05:06.49Z" },
{ url = "https://files.pythonhosted.org/packages/8e/5b/dda7d660fa5d5b6e228dcfc6be3664a2442d1601481686052af2da642e5e/livekit-1.1.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:299146efefad5f67751cd15b8225bae759be0d7ad2f0b4ae1a22c15860d93cf9", size = 10042499, upload-time = "2026-07-31T14:05:08.563Z" },
{ url = "https://files.pythonhosted.org/packages/21/e3/d9255eeaf205f090d63d762e5254097b62af394bfaa90106f71f1fb6740e/livekit-1.1.14-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:80962c4a22ddbf0e0ebd3563fc090fce42df66b39b90de68b161b7db01970f68", size = 11445915, upload-time = "2026-07-31T14:05:10.628Z" },
{ url = "https://files.pythonhosted.org/packages/f7/0a/514fb230e7c7f13ae7e53b9e39a6dd9ea1aa9ff5be9e588d55301d159a1e/livekit-1.1.14-py3-none-win_amd64.whl", hash = "sha256:b8f8d38f131956297923e520bc4375bc9ebfa255cab7f125cb7755bfca71df24", size = 10766643, upload-time = "2026-07-31T14:05:12.716Z" },
{ url = "https://files.pythonhosted.org/packages/ad/08/11d76335c9f2fb86dc34309f2829b33a40bb452477658aee131f50691594/livekit-1.1.21-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:76c29bc95b771fb37a8f32371163eb6f48404cb880ad27bcadfbf2a5801514aa", size = 10489770, upload-time = "2026-10-16T03:58:57.172Z" },
{ url = "https://files.pythonhosted.org/packages/56/e0/1703a82d8a0005fcaa36236ed6fc122f865456aceaffff9e94f3e5132a08/livekit-1.1.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b08c79ceaa5724a8a3d115c66f448fdc33a31c4cda883110d742209ec98a777c", size = 9312380, upload-time = "2026-10-16T03:59:00.083Z" },
{ url = "https://files.pythonhosted.org/packages/fa/ec/305511fc9c3041881730d46bbf6969a6bf57921237f09afd14ea7bcd5fbc/livekit-1.1.21-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4a2b036ba93a8d640b3770d62de1a9ee315824c9d2a0d7fbe1d1f26ecfa9ec6e", size = 10607086, upload-time = "2026-10-16T03:59:02.703Z" },
{ url = "https://files.pythonhosted.org/packages/e5/80/4078ee9bebe69ac35cd2c0b3a20f80862f71421d3fd8f3d28d4e1f735d98/livekit-1.1.21-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:148553a7d6515cf0c373f591f962de221253fe305d0a84857f2955f520a54340", size = 11836431, upload-time = "2026-10-16T03:59:06.101Z" },
{ url = "https://files.pythonhosted.org/packages/79/d9/af91fb1f33435bcc880116a0e6d16e0c8910d53b77204fc17099cd3099c4/livekit-1.1.21-py3-none-win_amd64.whl", hash = "sha256:0f4bafc5d51cf686dbe308762fa4431e89503ee8348539924438c305a8019a54", size = 10995805, upload-time = "2026-10-16T03:59:08.99Z" },
]
[[package]]
name = "livekit-agents"
version = "1.7.0"
version = "1.8.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiofiles" },
@@ -933,6 +963,7 @@ dependencies = [
{ name = "colorama" },
{ name = "docstring-parser" },
{ name = "eval-type-backport" },
{ name = "httpx2" },
{ name = "json-repair" },
{ name = "livekit" },
{ name = "livekit-api" },
@@ -957,9 +988,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "watchfiles" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/25/242c1e0fcaef5486be838b8535401baa8ffea6cbe1be5c9a8ab0f367e1f7/livekit_agents-1.7.0.tar.gz", hash = "sha256:3cc8ec39ed0c63f09d94cb170d95284f4a2fd63ebe608cb5f40c5b6e08a7d52a", size = 2659034, upload-time = "2026-08-20T18:16:20.231Z" }
sdist = { url = "https://files.pythonhosted.org/packages/59/3b/c4d63043dc2800ea1ac5bbf3f1ae60a85fe171cb45792d9fd05bcd887420/livekit_agents-1.8.9.tar.gz", hash = "sha256:5fd70b90accb9798e3a9a9b983c3e02cd564a54f1b995755d7a2aac21f50c92e", size = 2841661, upload-time = "2026-10-24T02:32:39.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/20/f83b5b89046514b80502b52f2a6ce70a8ce14d32902c499c385d2b1595d6/livekit_agents-1.7.0-py3-none-any.whl", hash = "sha256:416d73e7c9ae85d118b4feb9b9c5432d26c134abb537139bc44e0f9faa79ac11", size = 2773768, upload-time = "2026-08-20T18:16:17.744Z" },
{ url = "https://files.pythonhosted.org/packages/79/91/70f0b1ad97ed6de0cadd39b0dac28bfc1d2c4255c91d9633098ce8065132/livekit_agents-1.8.9-py3-none-any.whl", hash = "sha256:8ea824ceb5cb434d7a9ad3bbc8265c673659f93ac5857ee2f18224f6a52670fb", size = 2974420, upload-time = "2026-10-24T02:32:37.263Z" },
]
[package.optional-dependencies]
@@ -1034,15 +1065,15 @@ wheels = [
[[package]]
name = "livekit-plugins-deepgram"
version = "1.7.0"
version = "1.8.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "livekit-agents", extra = ["codecs"] },
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bd/7d/54ade407a92a83b1de9cce2abe247b3f0906ac3297aaba87d2cf529335f2/livekit_plugins_deepgram-1.7.0.tar.gz", hash = "sha256:adfd12f8636a0f4c52422f75942b929667f1b654fa1ec2903c43297bd3087035", size = 24698, upload-time = "2026-08-20T18:16:48.635Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/40/342f6867b6ae5909cea999c303e9bc06826b3dc36d28847d8f676c28a763/livekit_plugins_deepgram-1.8.9.tar.gz", hash = "sha256:9381a428e0ce5d0969f74a5aed40e2178e3ec074738d579191fcf34d1d0bd7af", size = 26868, upload-time = "2026-10-24T02:33:04.551Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/0d/d715b74b4a8f1373e298f62912c306a3ff534590daaf42643172488da09d/livekit_plugins_deepgram-1.7.0-py3-none-any.whl", hash = "sha256:8c9f9e6298950f1981dc99e35bc2038c750327062a46240a03f3b6ac05f8047f", size = 31799, upload-time = "2026-08-20T18:16:47.507Z" },
{ url = "https://files.pythonhosted.org/packages/a0/07/919cb5e3536ddaefccdd0cfcd9e0862c9fbadbbada41331fe1c64ef8350e/livekit_plugins_deepgram-1.8.9-py3-none-any.whl", hash = "sha256:3a08feb5c838680557bc4bfef33f6eb2fbb0a870208d4973c05ee3d643bbe80d", size = 34017, upload-time = "2026-10-24T02:33:03.237Z" },
]
[[package]]
@@ -1061,29 +1092,29 @@ wheels = [
[[package]]
name = "livekit-plugins-silero"
version = "1.7.0"
version = "1.8.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "livekit-agents" },
{ name = "numpy" },
{ name = "onnxruntime" },
]
sdist = { url = "https://files.pythonhosted.org/packages/87/34/388ba8cb1db86839228e88b799776d74db2286dbd2289ded744ec6b7d8dc/livekit_plugins_silero-1.7.0.tar.gz", hash = "sha256:f18aa1e980bc6892613a906ebfacd058160db3fb6db1dcd2a7b9d809dc9c0492", size = 1955425, upload-time = "2026-08-20T18:18:10.686Z" }
sdist = { url = "https://files.pythonhosted.org/packages/46/cb/02bd1ee5856f13ada3ab9414a51366a0b70dd013f472899964615de96394/livekit_plugins_silero-1.8.9.tar.gz", hash = "sha256:7b5fdf4eb53114e420c14af3c116855ca1ff6a80c4c8456e6eef50c3c49b2748", size = 1955455, upload-time = "2026-10-24T02:34:51.943Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2d/e1/5db37c5ca78bd011938f8d60ed6af984e61cd076a4535c56287cfb0d256b/livekit_plugins_silero-1.7.0-py3-none-any.whl", hash = "sha256:ce29626179d6a474b6b7fd6eb4dddb81e23a3a6a512e0013e8e3d07fda798e54", size = 3903143, upload-time = "2026-08-20T18:18:08.947Z" },
{ url = "https://files.pythonhosted.org/packages/8b/58/a26b4a9ffe19e9426e25223ec6e5d8f3cf0d0a8dd7ebdff59d688fc239e2/livekit_plugins_silero-1.8.9-py3-none-any.whl", hash = "sha256:afd32a25eab346a5258714edbadd13a56f8ad902ee4c1d8f928eeb1d971a64cf", size = 3903174, upload-time = "2026-10-24T02:34:49.86Z" },
]
[[package]]
name = "livekit-protocol"
version = "1.1.27"
version = "1.1.29"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
{ name = "types-protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/15/f3/5572f2b58f04534e22e266e113cb49b4f0b2a4e1d2abf385267cb51559ab/livekit_protocol-1.1.27.tar.gz", hash = "sha256:740a4c6e438da763d204f2a6b0fc8f7f33ca96e27b17ce9abc6557dbb0a28dbd", size = 124650, upload-time = "2026-09-16T12:43:25.761Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/fe/7020798cb6c2e35bdb688187e2205e320c0745c9e6ad2700a7644c6e61ff/livekit_protocol-1.1.29.tar.gz", hash = "sha256:879c63bff6a0ece5dcc94ba1a9cc4e79ce43897d676369d797d30e1f7444ddd9", size = 132938, upload-time = "2026-10-26T20:45:58.548Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/04/12a047d5ec0ea5099426d071a0147118767cb231d270cb1d0bece266eb01/livekit_protocol-1.1.27-py3-none-any.whl", hash = "sha256:765bbe0c9c6b626519f6f5952793f81eaf3f5b8c9bd1e1b03994f3177774aa10", size = 151314, upload-time = "2026-09-16T12:43:24.327Z" },
{ url = "https://files.pythonhosted.org/packages/8a/22/449d1d09e2d31458b559f63cd64a417bdf18c588d03dd75785dc10b0df9f/livekit_protocol-1.1.29-py3-none-any.whl", hash = "sha256:0721b54cb2745fa536fc2b1ae4fc74d7dab3ce0bdfb0dd579f89cc799928fe2b", size = 160814, upload-time = "2026-10-26T20:45:56.899Z" },
]
[[package]]
@@ -1438,133 +1469,163 @@ wheels = [
[[package]]
name = "openai"
version = "2.54.0"
version = "3.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
{ name = "httpx2" },
{ name = "jiter" },
{ name = "pydantic" },
{ name = "sniffio" },
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9f/7b/e01d5288d13e3bae3714ed39df402f99f88f422dcd978c5ce94848037200/openai-3.42.1.tar.gz", hash = "sha256:4222d919374648ff572753626e942f26eb1ece8a5e0e9a2708c9be4d560f070f", size = 2356422, upload-time = "2026-11-01T20:57:02.607Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" },
{ url = "https://files.pythonhosted.org/packages/fd/11/37b847d8a057000193974eeea1bcc681355ae50d35865f2eb2b323de6153/openai-3.42.1-py3-none-any.whl", hash = "sha256:c1959518a8e26f81a3564357082cb684540be639a21fece35ac5619415a3eadc", size = 2397433, upload-time = "2026-11-01T20:57:00.622Z" },
]
[[package]]
name = "opentelemetry-api"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" }
sdist = { url = "https://files.pythonhosted.org/packages/19/14/cf3a6b6448849656e1e5f4e2101f3988dc6789724050096aba1bc5ccf592/opentelemetry_api-1.45.2.tar.gz", hash = "sha256:d7b0008e21e5fd61cbe7a7f34ca3b6905a78ab677d9bc395e299359c75cda6b4", size = 73277, upload-time = "2026-10-29T17:29:37.888Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" },
{ url = "https://files.pythonhosted.org/packages/a9/2d/610db864d7513158cebb8f36cc88e67e73bd6c7aa1905ef18d89e1281c81/opentelemetry_api-1.45.2-py3-none-any.whl", hash = "sha256:2cbd737377e710ee740d261c94e93fa0f08e6d037c18f17513cc0a2d94479a34", size = 60335, upload-time = "2026-10-29T17:29:06.955Z" },
]
[[package]]
name = "opentelemetry-exporter-http-transport"
version = "0.66b2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a6/e6/dbeb3e217418071656232a4028c791060d219f218cef1249b664049d12c0/opentelemetry_exporter_http_transport-0.66b2.tar.gz", hash = "sha256:66d6ed810a515432c8891da9a834464332fd70badeca45bdeeae81b4280c903b", size = 11693, upload-time = "2026-10-29T17:29:39.444Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/b2/c622a101f31c0e6670b82f4264c4e77c161bce415bc6fc158fda0b82e218/opentelemetry_exporter_http_transport-0.66b2-py3-none-any.whl", hash = "sha256:fd5f5b490c099acda1cd7b909d5e8981436bf18cc8c6098d302d114c271720e9", size = 12154, upload-time = "2026-10-29T17:29:10.244Z" },
]
[package.optional-dependencies]
requests = [
{ name = "requests" },
]
[[package]]
name = "opentelemetry-exporter-otlp"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f2/45/7af37fe54e5d3e66e7dcd7ba8b8aeee73f202bfac909cc94b8c4e428f9ac/opentelemetry_exporter_otlp-1.44.0.tar.gz", hash = "sha256:af1cde7c33ea8ed624bf04ac49a885730fe44c1f1ad698656e592c38f70ce106", size = 6090, upload-time = "2026-07-16T15:25:34.585Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/45/52e9ce585d825e14cc211871acc3bc46b6cd36cdb0c09255a7feea98322b/opentelemetry_exporter_otlp-1.45.2.tar.gz", hash = "sha256:879044ba65b8b1be7a0d9d904afb76700e96d2a4d36700918d9325c9ee3b8be4", size = 6093, upload-time = "2026-10-29T17:29:40.905Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/c3/7b466a9463944e70b37b744072a0c1b88a425dade3fff0631adec66c9bcc/opentelemetry_exporter_otlp-1.44.0-py3-none-any.whl", hash = "sha256:4a498fa8d8fd8be9e8e2d175fe5524a3fe581ccffadd8509db86526a5fb97051", size = 6727, upload-time = "2026-07-16T15:25:14.445Z" },
{ url = "https://files.pythonhosted.org/packages/19/27/fcc3b725ddbfa274fbf6098c9583032557671d35aa8d5e08d7e7277fb126/opentelemetry_exporter_otlp-1.45.2-py3-none-any.whl", hash = "sha256:52c8b9f268289c92c08b3a97f48c63d6e5c037f25ca1f6f29ef162fb78974afc", size = 6732, upload-time = "2026-10-29T17:29:12.653Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-common"
version = "0.66b2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/3c/3f2cc8c42bde2620e65024d71e2eb025f7bd0821bb363b52bcc5a3260dbd/opentelemetry_exporter_otlp_common-0.66b2.tar.gz", hash = "sha256:06f5a9b88638e310efa1f1d206cdf6b75393e112aba4e174a963416192bd1028", size = 14325, upload-time = "2026-10-29T17:29:41.701Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/47/b3049e30b339c82e3c7ec1fc692185a5d5a3ba65a94c570bb39b2c0b8424/opentelemetry_exporter_otlp_common-0.66b2-py3-none-any.whl", hash = "sha256:ace86efb75489f925f1ef809856ae5f0c7db534e4afdc624530222228b1236a5", size = 12385, upload-time = "2026-10-29T17:29:13.667Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-proto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/09/4d717852c1cf3f854b76c7110a5d00883bc3c99288b9b0dbcbeb9e306eb6/opentelemetry_exporter_otlp_proto_common-1.44.0.tar.gz", hash = "sha256:dc87a5a5bc58f149a56d1547e4691588fa12994cdc3bc039a694ccb3375862ac", size = 20202, upload-time = "2026-07-16T15:25:37.658Z" }
sdist = { url = "https://files.pythonhosted.org/packages/69/58/239c98b36d9ea317098769957043c251703636a26189c884fba46c1dad84/opentelemetry_exporter_otlp_proto_common-1.45.2.tar.gz", hash = "sha256:f1f2806f3f28ce7938033823e50934fcd91595a39de38e69a28a51e19f40fc6d", size = 18873, upload-time = "2026-10-29T17:29:44.496Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/71/65fd9d54c10b860f87c045ccee1264cab7011268895d3528818a29c1172a/opentelemetry_exporter_otlp_proto_common-1.44.0-py3-none-any.whl", hash = "sha256:9a9fe61bba73d802904bc989f1d6b4a7b1ee40f06c40e98d6f85af65aaebb694", size = 17045, upload-time = "2026-07-16T15:25:18.201Z" },
{ url = "https://files.pythonhosted.org/packages/e6/d6/0f30d38120a949aef70528f845bf539f80df62abb4b8e62fdf69c2b6df5a/opentelemetry_exporter_otlp_proto_common-1.45.2-py3-none-any.whl", hash = "sha256:af483b24217028892f017096731b794b196b785025d2e6808874720fd0b05a80", size = 15392, upload-time = "2026-10-29T17:29:17.913Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-grpc"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
{ name = "grpcio" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-common" },
{ name = "opentelemetry-exporter-otlp-proto-common" },
{ name = "opentelemetry-proto" },
{ name = "opentelemetry-sdk" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1f/47/80d9e9d468dc5de3af5096f5ccdb065fa4dd1470f74495cc53e59e397f47/opentelemetry_exporter_otlp_proto_grpc-1.44.0.tar.gz", hash = "sha256:40d1ae9e03fcc36de3cbac610cc99f35894938bff9cfd90fc4ec68bd85448463", size = 27225, upload-time = "2026-07-16T15:25:38.308Z" }
sdist = { url = "https://files.pythonhosted.org/packages/46/2c/996861b327ddde01d3c0971582ebcd3f01a376acc720f7b0cb5274df4b70/opentelemetry_exporter_otlp_proto_grpc-1.45.2.tar.gz", hash = "sha256:9e4a6490ca7711479fec7c519425bc09808abbc9a2a1775528edecf200d19a5a", size = 25995, upload-time = "2026-10-29T17:29:45.2Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/29/6ae42ba32b153ae0a44ae125f0caff2188bbe62d99c82d1768da30864e72/opentelemetry_exporter_otlp_proto_grpc-1.44.0-py3-none-any.whl", hash = "sha256:6a1a645ea182a2f59440c51fa8301d309f3324a8f9d65f8395584b064b67ee4e", size = 19624, upload-time = "2026-07-16T15:25:19.096Z" },
{ url = "https://files.pythonhosted.org/packages/e8/48/14301cac3d106e795eef7e89e191489f086b4e5ad26e47c9635a198e95f4/opentelemetry_exporter_otlp_proto_grpc-1.45.2-py3-none-any.whl", hash = "sha256:8a48d60835e90536846bd82de487bfbd81bccb5a1b3d0fb82edeea17db07395c", size = 19490, upload-time = "2026-10-29T17:29:18.934Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-http"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-http-transport", extra = ["requests"] },
{ name = "opentelemetry-exporter-otlp-common" },
{ name = "opentelemetry-exporter-otlp-proto-common" },
{ name = "opentelemetry-proto" },
{ name = "opentelemetry-sdk" },
{ name = "requests" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1a/87/95e2a5aaa795b4e2260d74e16df2d5541deb2ea9de010bcd615f4dee2654/opentelemetry_exporter_otlp_proto_http-1.44.0.tar.gz", hash = "sha256:c633d7270ad6b57cd4cfbe8b0007a9e2e7c0cb50bd6c50fe2a7b245f721a09d8", size = 25806, upload-time = "2026-07-16T15:25:39.162Z" }
sdist = { url = "https://files.pythonhosted.org/packages/db/07/b7b01ecb69d02f540e679a9e36f7413d081f225bbbed4469d212ada10bb7/opentelemetry_exporter_otlp_proto_http-1.45.2.tar.gz", hash = "sha256:5984a3fb46bc4628e4e2b85d4a6e72637e03962aae6cf372791137768a4ad939", size = 28843, upload-time = "2026-10-29T17:29:46.15Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cd/d0/fdeb1a98d8d3a6205f5f297c51b4a9bfe65126ab60339669bbe3dd54c2e2/opentelemetry_exporter_otlp_proto_http-1.44.0-py3-none-any.whl", hash = "sha256:838592fce774c1c8bb7b9a0a7facbfa82e17be5a8a4e94cef10cb84ae026bae3", size = 21850, upload-time = "2026-07-16T15:25:20.006Z" },
{ url = "https://files.pythonhosted.org/packages/32/65/f64d7d2450217e2989714e1dee15e19bf79a77a313387abac0cac77bc10e/opentelemetry_exporter_otlp_proto_http-1.45.2-py3-none-any.whl", hash = "sha256:6e4d025138829d0ee57946930c2ccad6491ad4d92a958e9c7eea69278fe60cfc", size = 22180, upload-time = "2026-10-29T17:29:20.154Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/64/01/40ac4ae9a149263cc52c2cee200ddd80cb6d8db1a4610abf8eabce0fe771/opentelemetry_proto-1.44.0.tar.gz", hash = "sha256:c547a79c2f8c0c515d31509154682e5921c7cfd5ca67b70e1f9266e2c3e103f3", size = 46488, upload-time = "2026-07-16T15:25:45.34Z" }
sdist = { url = "https://files.pythonhosted.org/packages/24/9c/1aa4ce60e9bc72af20dc49c92cc0566825092d92b3d6a277439fb5fb7c01/opentelemetry_proto-1.45.2.tar.gz", hash = "sha256:bd8d23cf770605b0af9b7be2071d26a464fea289d35429abfd2a66226114ad64", size = 46486, upload-time = "2026-10-29T17:29:53.007Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/7c/8be563d68e93bbefa5c8affb82ddcff91b3ad858ce49957ba7b16fd3e0ab/opentelemetry_proto-1.44.0-py3-none-any.whl", hash = "sha256:898b155a0e1557afd867478fb6158e8122a46329ca0bb8dc53cc55e98f017f56", size = 72483, upload-time = "2026-07-16T15:25:28.429Z" },
{ url = "https://files.pythonhosted.org/packages/7e/0d/a0b8f2b9efe2ec1abdf639d3696db9b1563ebeb11d91aac08ba7341e9122/opentelemetry_proto-1.45.2-py3-none-any.whl", hash = "sha256:03a7ca1b43f89fd5b965d5975902bb2515c0c86e2428b456836e9a6149c6235a", size = 72484, upload-time = "2026-10-29T17:29:31.403Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.44.0"
version = "1.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/77/a6592cbc7c8d9bcc9d6757a9df45e04a7c585e3e6e7a13456da522b21109/opentelemetry_sdk-1.44.0.tar.gz", hash = "sha256:cebe7f65dc12f26ead75c6064de12fd2a9052e5060c0272d402cfa203aae123b", size = 208624, upload-time = "2026-07-16T15:25:46.078Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/1a/74a21d4d6969fb8b432ac397e66112a8167a6deb267ea4559dbaf2fe91ef/opentelemetry_sdk-1.45.2.tar.gz", hash = "sha256:7eec4de13dd21ff3a1670dcb0e02079e692736f94d733dfdce5af461d884248d", size = 221705, upload-time = "2026-10-29T17:29:54.685Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/23/ff077e61886ee020a17ce9c8b6fa11c601c8d8345b09ea24f605445df62a/opentelemetry_sdk-1.44.0-py3-none-any.whl", hash = "sha256:df081c4c6bcfdb1211e3e86140376792643128a25f8d72d1d27675936e7e96ad", size = 137221, upload-time = "2026-07-16T15:25:29.534Z" },
{ url = "https://files.pythonhosted.org/packages/9f/a5/9a3b793ef48245f2336a16beb2324c2397dbb3f987cd68942f77cca06156/opentelemetry_sdk-1.45.2-py3-none-any.whl", hash = "sha256:ff974ea70ee4f1d51e2ec152db37c2b626c7bf82bb3f7d86114fd8704e7199c6", size = 141756, upload-time = "2026-10-29T17:29:33.722Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.65b0"
version = "0.66b2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8f/73/0cbdebcb4cf545fdd328da14f5137e37d0770c3f26185e478b0d15d94f50/opentelemetry_semantic_conventions-0.65b0.tar.gz", hash = "sha256:f9b2b81e9d5b64f11bc952075e7e9c7fb0aab075c7fd1c46d597f1b919852d60", size = 148774, upload-time = "2026-07-16T15:25:46.902Z" }
sdist = { url = "https://files.pythonhosted.org/packages/37/3f/50f95972b50e8e353f5b9342034c9cd17730f1956f009ce6a3fbbd89f9ce/opentelemetry_semantic_conventions-0.66b2.tar.gz", hash = "sha256:7da5f1a5dc4383eb66711d4d3d3e71824d9403ac1559fea021cc103c1a4c4976", size = 150246, upload-time = "2026-10-29T17:29:55.682Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/0e/49df70d9b81fb5cbae4bbf2a49d865b09bcbcbc4eb53f5851b1027738d78/opentelemetry_semantic_conventions-0.65b0-py3-none-any.whl", hash = "sha256:1cacde7b0ad306f84c5ef08c3dbe1bbaf20165bba6f8bff43b670e555a086bcb", size = 204645, upload-time = "2026-07-16T15:25:30.688Z" },
{ url = "https://files.pythonhosted.org/packages/67/bd/6bf33ed31521c565931c037e9e81bac47c1acb000e850c6e24220e2a0c00/opentelemetry_semantic_conventions-0.66b2-py3-none-any.whl", hash = "sha256:4cbe61e5f9275d57ab31708446e7938ef4291ebbe6a7fca6f7a629c5acae87d8", size = 206280, upload-time = "2026-10-29T17:29:35.062Z" },
]
[[package]]
@@ -1698,17 +1759,17 @@ wheels = [
[[package]]
name = "protobuf"
version = "7.36.0"
version = "7.36.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a7/e7/0553e21d25ca4d9f573135775348a372c3ec34a93a71d5f297c3bac38341/protobuf-7.36.0.tar.gz", hash = "sha256:e8e09cb0d794c6687926fa558a8a6e72aa10edb997d5ca61da0765f12a3e00ea", size = 510034, upload-time = "2026-08-20T16:34:01.071Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737, upload-time = "2026-09-17T20:07:59.326Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/ae/58e3ca96cb2e118cc546b677359b3c6659f79a140935c08dec94c7998585/protobuf-7.36.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:9103532dffd80c6fab7e50c65a31007680a06eb57537d437bb1b35812c138a37", size = 453256, upload-time = "2026-08-20T16:33:53.945Z" },
{ url = "https://files.pythonhosted.org/packages/f0/15/5162230af4912697f0fe406f6800f80760945babcff0e2c2fe6c84ef2d5d/protobuf-7.36.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:bf94a5917c71058262de683669bc0a797a7669d3de71f0b36d058e3194f47b44", size = 341436, upload-time = "2026-08-20T16:33:55.134Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/1670b2bfc9a45e807e520c3e9be36524db9ccc7dc05ea17af7681cabdc61/protobuf-7.36.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:3297e60abdff301e5f74393d87f6cc59dacab5f024a89548a6e8de1d26576b16", size = 354440, upload-time = "2026-08-20T16:33:56.077Z" },
{ url = "https://files.pythonhosted.org/packages/c7/f8/bd5804695ba400e423c33fd4d9f58c28d86633d5ba1945c36ff3967d98cb/protobuf-7.36.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:70f5ec8eb0da81a44360c0dc0beac99a0d78071d21956a7076bae8bd2051841b", size = 340439, upload-time = "2026-08-20T16:33:56.992Z" },
{ url = "https://files.pythonhosted.org/packages/ef/9f/acd02338235a3e7d03168c4303478347b7624fc8189ff4e7f0d2654bbe86/protobuf-7.36.0-cp310-abi3-win32.whl", hash = "sha256:7326fd717bdc419162a735938d89d4032332bcc3408804012b24ff3a37086071", size = 440216, upload-time = "2026-08-20T16:33:57.99Z" },
{ url = "https://files.pythonhosted.org/packages/0e/4e/12cb93270967a2affff5b3f720694700d4d87712a67afd05c8cb3f6fa52c/protobuf-7.36.0-cp310-abi3-win_amd64.whl", hash = "sha256:1781cc1de61249b750848029bca452c0a8b7e990080316b9bbc2518b2117b488", size = 453731, upload-time = "2026-08-20T16:33:58.951Z" },
{ url = "https://files.pythonhosted.org/packages/01/c3/629999e78d46c1115c11886d51c6bd68c17ce4a944f1ea3e153a91316a33/protobuf-7.36.0-py3-none-any.whl", hash = "sha256:53374d53fc29a67f7dbbf0ade47d7526a0f0137bf0f9c90e48d8a60790ef748c", size = 177024, upload-time = "2026-08-20T16:34:00.053Z" },
{ url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039, upload-time = "2026-09-17T20:07:51.542Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219, upload-time = "2026-09-17T20:07:52.914Z" },
{ url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223, upload-time = "2026-09-17T20:07:53.985Z" },
{ url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223, upload-time = "2026-09-17T20:07:54.931Z" },
{ url = "https://files.pythonhosted.org/packages/fc/1b/dcc64f358fcb51811b58ae40b3d28f820725f116d86487cc20bd4b130701/protobuf-7.36.2-cp310-abi3-win32.whl", hash = "sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728", size = 442998, upload-time = "2026-09-17T20:07:55.826Z" },
{ url = "https://files.pythonhosted.org/packages/8a/55/b77bda4e5e5f5971fb51b07663694690e9afdb9402136c16a522bd621cad/protobuf-7.36.2-cp310-abi3-win_amd64.whl", hash = "sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353", size = 456514, upload-time = "2026-09-17T20:07:57.188Z" },
{ url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806, upload-time = "2026-09-17T20:07:58.211Z" },
]
[[package]]
@@ -1870,11 +1931,11 @@ wheels = [
[[package]]
name = "python-dotenv"
version = "1.2.3"
version = "1.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
sdist = { url = "https://files.pythonhosted.org/packages/74/26/2fbeedb218a787a5eea551c7532cac4e009f83d689dd2faa0d0353473f86/python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0", size = 60824, upload-time = "2026-10-01T05:36:10Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
{ url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" },
]
[[package]]
@@ -1953,27 +2014,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.16.4"
version = "0.17.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/58/690cf56da80c8f2c8bf12042b6017beac2c34fe961f65420bcb3fb7fac38/ruff-0.17.2.tar.gz", hash = "sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb", size = 5333400, upload-time = "2026-10-22T18:38:00.181Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" },
{ url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" },
{ url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" },
{ url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" },
{ url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" },
{ url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" },
{ url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" },
{ url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" },
{ url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" },
{ url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" },
{ url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" },
{ url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" },
{ url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" },
{ url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" },
{ url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" },
{ url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" },
{ url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
{ url = "https://files.pythonhosted.org/packages/98/b8/8b6d69c57e8fe1cc6af1aa7493b72a1a5b5a8afe34694e302025441729d4/ruff-0.17.2-py3-none-linux_armv6l.whl", hash = "sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e", size = 10183857, upload-time = "2026-10-22T18:37:14.956Z" },
{ url = "https://files.pythonhosted.org/packages/e8/61/12cc2ff7b2fa90a268ce1e2162c23aaf76e601d22623f6a31b0eeaa764b0/ruff-0.17.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d", size = 10210904, upload-time = "2026-10-22T18:37:17.985Z" },
{ url = "https://files.pythonhosted.org/packages/e8/7c/b8850a8e4ad29f64a827dd6c6902db4a9aa538fb2b9eef4e26084001a3bb/ruff-0.17.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7", size = 9408439, upload-time = "2026-10-22T18:37:20.964Z" },
{ url = "https://files.pythonhosted.org/packages/b7/b6/6a64fae86ca5e5e29d5adc311e5481de726cedf9aee607435a16c31c442a/ruff-0.17.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac", size = 10047078, upload-time = "2026-10-22T18:37:23.717Z" },
{ url = "https://files.pythonhosted.org/packages/10/19/23d376547715175aa1bd46df48df6aacc7c6439d9daf19cac15975180aa5/ruff-0.17.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160", size = 9990478, upload-time = "2026-10-22T18:37:26.584Z" },
{ url = "https://files.pythonhosted.org/packages/93/6c/34cda32cbe13adac5c6b291a307a5570d8ebd37c0c86339f0fe628e55632/ruff-0.17.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a", size = 10673624, upload-time = "2026-10-22T18:37:29.767Z" },
{ url = "https://files.pythonhosted.org/packages/bf/10/5d762a4dbc851d8bb47863cf3a28a61bf31c804d393b70d891fac899f5d1/ruff-0.17.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4", size = 11351797, upload-time = "2026-10-22T18:37:32.535Z" },
{ url = "https://files.pythonhosted.org/packages/3f/04/a0e92a81faf5c81c1c98d4374c8401734ccf2b8cca7d0170cb0fec08c6e2/ruff-0.17.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869", size = 10949414, upload-time = "2026-10-22T18:37:35.501Z" },
{ url = "https://files.pythonhosted.org/packages/c3/d8/59894cf630d2d86882457a2a6a0a1533410c5c805bfbac6f6eb4b5aef69d/ruff-0.17.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004", size = 10528000, upload-time = "2026-10-22T18:37:37.88Z" },
{ url = "https://files.pythonhosted.org/packages/ab/95/d6e30c51e851d37e10762ad91fa1e76ddaad998589b5667962a3cfbc9edd/ruff-0.17.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0", size = 10571779, upload-time = "2026-10-22T18:37:40.444Z" },
{ url = "https://files.pythonhosted.org/packages/f4/5a/a7e1d74fb464250137d3d4eca645d323536533b672004d04ee0e46e0caf3/ruff-0.17.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f", size = 10051707, upload-time = "2026-10-22T18:37:42.812Z" },
{ url = "https://files.pythonhosted.org/packages/6e/f2/02fca29ca7999380f0350a7212fe755c11ab9c22d61327356cce8decdbc6/ruff-0.17.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4", size = 9998227, upload-time = "2026-10-22T18:37:45.224Z" },
{ url = "https://files.pythonhosted.org/packages/d1/00/55aa604a093dfdc1526e570fae0bb7d37a2221c7b4728d5f080b2add16ba/ruff-0.17.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a", size = 10377692, upload-time = "2026-10-22T18:37:47.704Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5a/a7a640f47bb906ec8fa233ea84319d503e6dee02b59e3901bd312ebd1f9b/ruff-0.17.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe", size = 10790587, upload-time = "2026-10-22T18:37:50.191Z" },
{ url = "https://files.pythonhosted.org/packages/2c/d4/c3b74e1b4808438ea7f14bb914478025e2d51261f60774a54a0fa222ec46/ruff-0.17.2-py3-none-win32.whl", hash = "sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd", size = 9870537, upload-time = "2026-10-22T18:37:52.486Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/c7da96c2c96d20704cf9027f7a5121da272a8c05f1d1531ba949e3b3df04/ruff-0.17.2-py3-none-win_amd64.whl", hash = "sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c", size = 10282826, upload-time = "2026-10-22T18:37:55.2Z" },
{ url = "https://files.pythonhosted.org/packages/81/60/a3604c79cb02777b6cb84da894104021986f06c4bc41375102cc95049a32/ruff-0.17.2-py3-none-win_arm64.whl", hash = "sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962", size = 9827036, upload-time = "2026-10-22T18:37:58.031Z" },
]
[[package]]
@@ -1990,15 +2051,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.68.1"
version = "2.73.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/e7/c504a4bd2d95df2e0ab73714a9161ff1cf6ff1486922685e5f46dfd9eba8/sentry_sdk-2.68.1.tar.gz", hash = "sha256:6a97895230b04bc35d4d8d2e51e3b9e21902dfb0086ccf1f131a80c15c7b997a", size = 1019262, upload-time = "2026-08-24T13:09:38.108Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c5/3b/1e47acf2456781c26e12007379ecc154bf310b8f64d40390d78b791d55c3/sentry_sdk-2.73.0.tar.gz", hash = "sha256:4a2ab97b16e4693c325ab361c7bbbb346dbc7d0bc0150922d795a25bac8d5280", size = 1079078, upload-time = "2026-10-22T09:08:49.63Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/28/465ad9382be98f2172e691f5836cf87f936773913ad7ab85ba1ba1d6706e/sentry_sdk-2.68.1-py3-none-any.whl", hash = "sha256:775b78871783a0ffd758276ad01b3bb2b1ebcdad8f9d2f0a7723f76b73c99b65", size = 520851, upload-time = "2026-08-24T13:09:36.186Z" },
{ url = "https://files.pythonhosted.org/packages/b2/88/cabf98d017d7b940720e514e5f8ee02e3e6f7495caf93c30735a38387ec4/sentry_sdk-2.73.0-py3-none-any.whl", hash = "sha256:0bcfa6928ea5adbb89591858b514d27adac27c7a3cccc31b20ed10e0b09eda9e", size = 549011, upload-time = "2026-10-22T09:08:47.88Z" },
]
[[package]]
@@ -2045,15 +2106,12 @@ wheels = [
]
[[package]]
name = "tqdm"
version = "4.70.1"
name = "truststore"
version = "0.10.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" },
{ url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" },
]
[[package]]
@@ -2151,131 +2209,137 @@ wheels = [
[[package]]
name = "websockets"
version = "17.1"
version = "17.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" }
sdist = { url = "https://files.pythonhosted.org/packages/30/4b/4692be6d5bf739ed049b3813901c42091f8c054df89d5c089343823dea0d/websockets-17.3.tar.gz", hash = "sha256:1c7dd93a1c9f89297471493674b1fc1d9467cafd45374f2172213046bdb3a329", size = 188878, upload-time = "2026-10-19T16:58:05.813Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" },
{ url = "https://files.pythonhosted.org/packages/13/86/bc1317b1a4d8c4688e2a7e564b5e004dab44c2534d7ca05de6ae9a863fca/websockets-17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98", size = 214692, upload-time = "2026-08-26T14:55:46.366Z" },
{ url = "https://files.pythonhosted.org/packages/8f/e7/df821761772beaa48c211ee0e234930b35c1473778470773823f56d3911b/websockets-17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab", size = 214959, upload-time = "2026-08-26T14:55:47.885Z" },
{ url = "https://files.pythonhosted.org/packages/3e/92/c3fb72f11764812fc648bf3838d224972427b348e8b3989d9e0a9df87da3/websockets-17.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74", size = 224278, upload-time = "2026-08-26T14:55:49.241Z" },
{ url = "https://files.pythonhosted.org/packages/fb/05/9f82d090c8d2d861604147ef6dfb938a90b039f9358d5193f1df62558593/websockets-17.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6", size = 224557, upload-time = "2026-08-26T14:55:50.348Z" },
{ url = "https://files.pythonhosted.org/packages/8a/50/5cbf677b865290fe36819ff00615826e7edc1df38786f770123ff39a933d/websockets-17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08", size = 225791, upload-time = "2026-08-26T14:55:51.75Z" },
{ url = "https://files.pythonhosted.org/packages/c1/1c/eb8a032285243381b09a221ae384c972d5000453ad136add4d1595cec798/websockets-17.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11", size = 228574, upload-time = "2026-08-26T14:55:52.922Z" },
{ url = "https://files.pythonhosted.org/packages/69/85/413736251cb3ac04ce84cbd90e893d9a36a9698d4820b323aff3aa187e50/websockets-17.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4", size = 226428, upload-time = "2026-08-26T14:55:54.263Z" },
{ url = "https://files.pythonhosted.org/packages/d2/2b/a08bcc7fa1ca81a10f84ba32b6e6edd73a913f4b0c2640eed1fd626efacd/websockets-17.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8", size = 225184, upload-time = "2026-08-26T14:55:55.943Z" },
{ url = "https://files.pythonhosted.org/packages/e5/8a/3bd2d0cf6b148c8c866d5d9fdcde30c04bfd81fdfac86813e69377eb4448/websockets-17.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391", size = 222430, upload-time = "2026-08-26T14:55:57.103Z" },
{ url = "https://files.pythonhosted.org/packages/3b/c9/8e891ae342668735eabbbc669895e15195e4b45f24a4beeb58af76f414c7/websockets-17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a", size = 225227, upload-time = "2026-08-26T14:55:58.375Z" },
{ url = "https://files.pythonhosted.org/packages/e1/6f/c816f332dca11425e9bda7c07f7573eb5c5f8a735849d02b0d81e8ee20fa/websockets-17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837", size = 223831, upload-time = "2026-08-26T14:55:59.664Z" },
{ url = "https://files.pythonhosted.org/packages/53/67/5e91d5308ce24fc1ec74f56536c12f4888bad45ff5ea50f3180f8c518c57/websockets-17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641", size = 224600, upload-time = "2026-08-26T14:56:00.873Z" },
{ url = "https://files.pythonhosted.org/packages/bb/96/faa298ecf2570d35b0eb37caddf4992178d907e108ed74bfffb6bc092c29/websockets-17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090", size = 225707, upload-time = "2026-08-26T14:56:02.001Z" },
{ url = "https://files.pythonhosted.org/packages/0b/12/5710d2482ca5061c1eec5eb46f6313837c760d4115b1795c85b6c08be4e3/websockets-17.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1", size = 223263, upload-time = "2026-08-26T14:56:03.092Z" },
{ url = "https://files.pythonhosted.org/packages/27/47/0c30f4eebfd1d93fae779d268f678d48847fb98516f5200849574eee8820/websockets-17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067", size = 224244, upload-time = "2026-08-26T14:56:04.321Z" },
{ url = "https://files.pythonhosted.org/packages/41/33/46c256195a1255079ae23d1b1267b2e1843dc5f46a67f973cdf2a3523dff/websockets-17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60", size = 224520, upload-time = "2026-08-26T14:56:05.521Z" },
{ url = "https://files.pythonhosted.org/packages/06/9a/aef0792731df4352e5f417369b532b3325fe434765ca90c193f594ae1e67/websockets-17.1-cp312-cp312-win32.whl", hash = "sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb", size = 217485, upload-time = "2026-08-26T14:56:06.715Z" },
{ url = "https://files.pythonhosted.org/packages/50/23/493ecfdaf32898e5ea24dc900e33e5e317f9662d5d9ab2d44b2e111b4e1c/websockets-17.1-cp312-cp312-win_amd64.whl", hash = "sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec", size = 217786, upload-time = "2026-08-26T14:56:08.055Z" },
{ url = "https://files.pythonhosted.org/packages/97/3d/91954e2f7876f74ce1213e9b92c65a63b559cc4b942a931ebeb351cd9932/websockets-17.1-cp312-cp312-win_arm64.whl", hash = "sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729", size = 217711, upload-time = "2026-08-26T14:56:09.267Z" },
{ url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" },
{ url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" },
{ url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" },
{ url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" },
{ url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" },
{ url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" },
{ url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" },
{ url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" },
{ url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" },
{ url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" },
{ url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" },
{ url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" },
{ url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" },
{ url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" },
{ url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" },
{ url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" },
{ url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" },
{ url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" },
{ url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" },
{ url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" },
{ url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" },
{ url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" },
{ url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" },
{ url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" },
{ url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" },
{ url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" },
{ url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" },
{ url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" },
{ url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" },
{ url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" },
{ url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" },
{ url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" },
{ url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" },
{ url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" },
{ url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" },
{ url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" },
{ url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" },
{ url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" },
{ url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" },
{ url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" },
{ url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" },
{ url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" },
{ url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" },
{ url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" },
{ url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" },
{ url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" },
{ url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" },
{ url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" },
{ url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" },
{ url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" },
{ url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" },
{ url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" },
{ url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" },
{ url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" },
{ url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" },
{ url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" },
{ url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" },
{ url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" },
{ url = "https://files.pythonhosted.org/packages/de/d5/99a6c6a1eb5d5ae9f45f59a3c97f4e3b21f310eb404a547fb3e7d2fc054c/websockets-17.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f", size = 216970, upload-time = "2026-08-26T14:57:28.575Z" },
{ url = "https://files.pythonhosted.org/packages/a6/0e/1e7f6e833728193958d3ed3d67b5d57c3c7cfa948abf94d4bc553257c954/websockets-17.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2", size = 214699, upload-time = "2026-08-26T14:57:29.862Z" },
{ url = "https://files.pythonhosted.org/packages/07/00/95d39549f86e34425a0412bcbe61708dd1fc46af654e2134a6c4389102ad/websockets-17.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e", size = 214927, upload-time = "2026-08-26T14:57:31.148Z" },
{ url = "https://files.pythonhosted.org/packages/4c/ff/b442415fc4f7f9943b0fc8e8eebaa13923ca73361e167c439ba634eecbd9/websockets-17.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8", size = 224373, upload-time = "2026-08-26T14:57:32.833Z" },
{ url = "https://files.pythonhosted.org/packages/a8/dd/b83537aae4cf61615b9d8b2dbb235c0030ba85457a6d934798273814600f/websockets-17.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42", size = 224801, upload-time = "2026-08-26T14:57:34.041Z" },
{ url = "https://files.pythonhosted.org/packages/76/83/5ab0abed58454909e8dbab45086ac68ee4556d7a8ada26735addc909b903/websockets-17.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b", size = 225967, upload-time = "2026-08-26T14:57:35.292Z" },
{ url = "https://files.pythonhosted.org/packages/4b/26/e2412f2b998a8c1dfc00c0709ff6ee0c634dd0b0b4f92bdfe9667876b71c/websockets-17.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d", size = 227664, upload-time = "2026-08-26T14:57:36.493Z" },
{ url = "https://files.pythonhosted.org/packages/ec/25/0dd4495df3c0e02f6db705312ba85ab9b2dd42257dc23eb0da10066e4844/websockets-17.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb", size = 226447, upload-time = "2026-08-26T14:57:37.781Z" },
{ url = "https://files.pythonhosted.org/packages/be/67/6df3f63ffc48f08126ed0cd2fd2a41092967c3e364f8ec100deae90b6d77/websockets-17.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e", size = 225343, upload-time = "2026-08-26T14:57:39.133Z" },
{ url = "https://files.pythonhosted.org/packages/b1/8d/a8479bbb09ff054907d141123d8f52fb6ae5ac39c6dbe39e6a02a8408309/websockets-17.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80", size = 222748, upload-time = "2026-08-26T14:57:40.478Z" },
{ url = "https://files.pythonhosted.org/packages/40/fb/4c3d2a3269cde3f3087916de9c3d9fc5d7196b46846d8c3a9ae59ad0a884/websockets-17.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335", size = 225453, upload-time = "2026-08-26T14:57:41.859Z" },
{ url = "https://files.pythonhosted.org/packages/7f/1c/6467b401d19408f34e1c7389c222c2c7e1dfdf08c551190269b5eabc726c/websockets-17.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9", size = 224112, upload-time = "2026-08-26T14:57:43.136Z" },
{ url = "https://files.pythonhosted.org/packages/c5/5f/744e032ac80e11039a7447657ebabb46e9b5c2dbcec83be571335212932f/websockets-17.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774", size = 224646, upload-time = "2026-08-26T14:57:44.871Z" },
{ url = "https://files.pythonhosted.org/packages/9f/47/bcb9128d9afc4d0934d9192e2a24897ca2f7a63df2654904915349c6c46d/websockets-17.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6", size = 225797, upload-time = "2026-08-26T14:57:46.338Z" },
{ url = "https://files.pythonhosted.org/packages/c7/e0/b058047b7cf565e1105b10ef6b6b24a6ebe3575678c7dc75a645334705a7/websockets-17.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394", size = 223605, upload-time = "2026-08-26T14:57:48.175Z" },
{ url = "https://files.pythonhosted.org/packages/b9/69/fc1555bff884de363f1bf9eebf2836dbeb29fa7e4f957debb7bbcf43abba/websockets-17.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a", size = 224508, upload-time = "2026-08-26T14:57:49.407Z" },
{ url = "https://files.pythonhosted.org/packages/e7/f9/648d4e68621688b19093b06f7b497d520952e68cdea1c1b54371fe9491de/websockets-17.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4", size = 224767, upload-time = "2026-08-26T14:57:50.799Z" },
{ url = "https://files.pythonhosted.org/packages/58/93/f8342b55864f71df13eb8e9ef7dce691b87a87f04f75bb8a1385b3336e7c/websockets-17.1-cp315-cp315-win32.whl", hash = "sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927", size = 217003, upload-time = "2026-08-26T14:57:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f0/7b5fdb774c245e0b6217009e2a24d2105c1a64923949f33be41aa7959302/websockets-17.1-cp315-cp315-win_amd64.whl", hash = "sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc", size = 217300, upload-time = "2026-08-26T14:57:53.492Z" },
{ url = "https://files.pythonhosted.org/packages/76/33/1fe6ed1b5087516115ca451b2c240314b010647071f8fc3bd78a21e4dddb/websockets-17.1-cp315-cp315-win_arm64.whl", hash = "sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d", size = 217214, upload-time = "2026-08-26T14:57:54.88Z" },
{ url = "https://files.pythonhosted.org/packages/94/ca/ed02e75996a266d76c5fcb5dd9b930db4cf2b388ca5fa3d2a72086f81568/websockets-17.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13", size = 217282, upload-time = "2026-08-26T14:57:56.108Z" },
{ url = "https://files.pythonhosted.org/packages/bd/7d/d536f5bc89ea5b52fd1c1727c59fabafee6bc41f5ce92c3bd2f83047908c/websockets-17.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51", size = 214863, upload-time = "2026-08-26T14:57:57.355Z" },
{ url = "https://files.pythonhosted.org/packages/37/37/944cf17bad668e9be1247e6314f88a48b9faf7c250e383410db8b38af0b9/websockets-17.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e", size = 215073, upload-time = "2026-08-26T14:57:58.719Z" },
{ url = "https://files.pythonhosted.org/packages/74/bf/3267966cc1bbc2b8fa62fd329651b0af502df1f5d1c0eed027ff339d6aa8/websockets-17.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800", size = 225229, upload-time = "2026-08-26T14:58:00.235Z" },
{ url = "https://files.pythonhosted.org/packages/7f/d8/85ea722f483510abb39fc71aafb4465d17cf9051a275ab036874ff3c300c/websockets-17.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab", size = 225500, upload-time = "2026-08-26T14:58:01.994Z" },
{ url = "https://files.pythonhosted.org/packages/50/ce/64c7d00005bd0d15ecb5c5fcb7fb2597b6b92ddd16c4fa6bbc3d2835ad63/websockets-17.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7", size = 226829, upload-time = "2026-08-26T14:58:03.327Z" },
{ url = "https://files.pythonhosted.org/packages/b4/dc/096c67940fb957e667ca3c542818150434eb0388c6fdc90b3a502f3c3e96/websockets-17.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24", size = 228457, upload-time = "2026-08-26T14:58:04.78Z" },
{ url = "https://files.pythonhosted.org/packages/51/fe/f2331b6b7ccc67589891da354fa46a5cb79e95f83b9fd0e734d77f1f2140/websockets-17.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6", size = 227265, upload-time = "2026-08-26T14:58:06.102Z" },
{ url = "https://files.pythonhosted.org/packages/47/a5/fb1642302f8ec77ca922203074f155a9831a5128ad75e725059a476d1227/websockets-17.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623", size = 226143, upload-time = "2026-08-26T14:58:07.464Z" },
{ url = "https://files.pythonhosted.org/packages/d7/41/7133fcfb63f5562750b269d6a845c689dde6a2c6407286da395beea19ddd/websockets-17.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151", size = 223501, upload-time = "2026-08-26T14:58:08.766Z" },
{ url = "https://files.pythonhosted.org/packages/64/b1/82b36bfabc79ff2d383a1fc043cee6a13f794ef4f6bf1b4810ad6988cf6f/websockets-17.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8", size = 226330, upload-time = "2026-08-26T14:58:09.987Z" },
{ url = "https://files.pythonhosted.org/packages/41/7d/5b511b9bf6e9ad331e6ff902fcbcc71c3794d10ef3b5efe80ccb8f0a7861/websockets-17.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a", size = 224980, upload-time = "2026-08-26T14:58:11.303Z" },
{ url = "https://files.pythonhosted.org/packages/e0/50/aed08f25301f8eef23be903ff9319fcf35630ca2bdec9d226f7d804dd5b3/websockets-17.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2", size = 225478, upload-time = "2026-08-26T14:58:12.543Z" },
{ url = "https://files.pythonhosted.org/packages/3e/47/0d63d4168536b4682c9d19b7399443b1176f25dbb68878374fa716670230/websockets-17.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3", size = 226588, upload-time = "2026-08-26T14:58:14.457Z" },
{ url = "https://files.pythonhosted.org/packages/b3/dd/844bd0b6386fc81ed6a55f4b6dd26f01c6987eda205afa10175ea12b2164/websockets-17.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe", size = 224336, upload-time = "2026-08-26T14:58:15.778Z" },
{ url = "https://files.pythonhosted.org/packages/96/18/03709c84bc88ec4dcea68d4be4ccd07d611073dec111203a5bf45af8809d/websockets-17.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d", size = 225197, upload-time = "2026-08-26T14:58:17.141Z" },
{ url = "https://files.pythonhosted.org/packages/27/cf/0d1c694b6466c89e875b85b32b51312c472cf6708eee91914866f5087dde/websockets-17.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc", size = 225493, upload-time = "2026-08-26T14:58:18.521Z" },
{ url = "https://files.pythonhosted.org/packages/4e/f5/99857c3dd9676749f33e3668665a34ad6099505fb8d75eb084f49f7807a9/websockets-17.1-cp315-cp315t-win32.whl", hash = "sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3", size = 217130, upload-time = "2026-08-26T14:58:20.037Z" },
{ url = "https://files.pythonhosted.org/packages/2c/84/77599922ab441bfe61508f97dab2c71f8e114d31793993ea54011db16199/websockets-17.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110", size = 217448, upload-time = "2026-08-26T14:58:21.382Z" },
{ url = "https://files.pythonhosted.org/packages/ce/3c/8b9a225b523f06a9389be81f1b0ab07c49bec6014742e6aa359c1f920f1f/websockets-17.1-cp315-cp315t-win_arm64.whl", hash = "sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81", size = 217372, upload-time = "2026-08-26T14:58:22.807Z" },
{ url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" },
{ url = "https://files.pythonhosted.org/packages/0d/29/902ffe19c0d35603102f31490dea342f083bc77d8f8bcdc7d8b16f5810f9/websockets-17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:41665c7ea01d3ae497a0c61fd22d3424230d18782a5fceb79f04db9ccb2042c9", size = 218250, upload-time = "2026-10-19T16:54:54.718Z" },
{ url = "https://files.pythonhosted.org/packages/f0/66/f56a2637b1aac263205b0e3cd7b2ff2e2f8b404b8118c991f4d122fd381f/websockets-17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8f7469408740cf06a79b01605f2ae0b2acc8839c03fcbbf05da49baee12d3302", size = 215927, upload-time = "2026-10-19T16:54:56.124Z" },
{ url = "https://files.pythonhosted.org/packages/22/7d/a27f25451c4cc62141992b51005688481210f3d3e63de3dc66f9e882ba59/websockets-17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d68da281ac208b6236ffa8beef23fe7c7c956d8bf09b421d447232fd94441b35", size = 216196, upload-time = "2026-10-19T16:54:57.538Z" },
{ url = "https://files.pythonhosted.org/packages/9f/77/40bbc1a5c016cf9f598dea21d1250a93245c995eafaec6401d0e4eb4e647/websockets-17.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078d1d41f45ee34eed5a4f3bbe7c588caecbdd6e12dfca73e8c1b0e803773793", size = 225516, upload-time = "2026-10-19T16:54:59.104Z" },
{ url = "https://files.pythonhosted.org/packages/3d/9c/91889c8c62fb7d16cd58e3074473a259303d6229bf0ce496aec87e66f3dc/websockets-17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7f391838e8adcf1f2fe71561d9e5a9f74ea7ffa5052eb2db329aac677383136", size = 225795, upload-time = "2026-10-19T16:55:00.438Z" },
{ url = "https://files.pythonhosted.org/packages/b8/29/2e5e2158e9920f806cd45f3cbc81cf5f3eef2b9e5a74b9ef049e2a3578e3/websockets-17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c15a165673bb3fb57facbf040edf5c0556d20ccd2e0ccc5f23ebb69fb715fdc1", size = 227034, upload-time = "2026-10-19T16:55:02.065Z" },
{ url = "https://files.pythonhosted.org/packages/b6/af/fbab5c4098b26026b877bfc139b1ea0d7c6eaf7a86b84544933b4c445f82/websockets-17.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28d1935c16782a1e3650c9baa79069b016fff739e292e4cb14bbb830d6930491", size = 229863, upload-time = "2026-10-19T16:55:03.415Z" },
{ url = "https://files.pythonhosted.org/packages/ac/72/d6e460a991607794072fe3fdfcb3221a0342af61586f5bd7ea40bddcfd21/websockets-17.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c534c9524a2acd0b97b7022074bec3784ac273c683cf9535087e437672ea8531", size = 227669, upload-time = "2026-10-19T16:55:04.763Z" },
{ url = "https://files.pythonhosted.org/packages/ec/8f/1c7da1cfb632e141e019cf2e0bbbd4e6dc8c198c8cc29544e4acf1a89f56/websockets-17.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b459d29c8b82a420a98fa0ce8e9cf787ce0062d98cc90604ef9574fb3cc26ff8", size = 226429, upload-time = "2026-10-19T16:55:06.112Z" },
{ url = "https://files.pythonhosted.org/packages/3e/63/c48ef8bbfbd982f2cfae1cfcf9e63f5bfbb8468fd37ca700507efc0b0007/websockets-17.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53209dffd91b0aefed9ea630fa6ad5c2944d6210ba3a8a3a8664dc1575289b14", size = 223653, upload-time = "2026-10-19T16:55:07.473Z" },
{ url = "https://files.pythonhosted.org/packages/cb/37/7bedaad48ae8e89eaa4f3942c11be0c41de77ce2118a475f3ba05e9b5a0f/websockets-17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9662a9fe457820c60365b3209e03cbd8e4a817195d55de65a80fa680b4bea136", size = 226465, upload-time = "2026-10-19T16:55:08.791Z" },
{ url = "https://files.pythonhosted.org/packages/33/cf/ab9a8a0de4769ca1dce392021b6a922e21b4d3687fe9e1ab4db585a088f8/websockets-17.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5670a762c7ae388bf8aa04199fd436180a4d28318dca9c1eae7b38604676dbfd", size = 225071, upload-time = "2026-10-19T16:55:10.127Z" },
{ url = "https://files.pythonhosted.org/packages/89/96/64d8460cec95f873c61c6d13a09b435915150640525ae56d5871400aec09/websockets-17.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ed59cb4ee258674a6ed9b8515e42079db02cb3be94f133fdd3f200183a40c49d", size = 225841, upload-time = "2026-10-19T16:55:11.519Z" },
{ url = "https://files.pythonhosted.org/packages/a6/98/2b83211ee3087db4ae66533782664c0e283cf4a08cbb14036018d0803e09/websockets-17.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f78bb2e82fe88912c9d7b25c98e57a9b08655579af89f164fd4c0657a075d62", size = 226945, upload-time = "2026-10-19T16:55:12.85Z" },
{ url = "https://files.pythonhosted.org/packages/9f/fe/6b2a37d422d7f4dfc0c92c95a14649d9bd662243ed8b0774523ed2703604/websockets-17.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:71250e010eede33c1d638e2c7a98288520d67f782fd5eceecaeca3129233a09a", size = 224502, upload-time = "2026-10-19T16:55:14.48Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c5/364015c9311c89ea6085d54c70d654c5d27ba05014d44ef9fd9daa09462b/websockets-17.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:729989759feb81ff6493879a0d60a0bd7d2eee41001bbec0381a080318439382", size = 225483, upload-time = "2026-10-19T16:55:15.899Z" },
{ url = "https://files.pythonhosted.org/packages/c2/b3/75d0cae8398816ed34a16a78252bfc8651f2f883cc5a568e1198395f15df/websockets-17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90d8c4fa5ba293cf92de351fbc292cdaadb1a1d58d031a1a97ff9b8fb6250ec7", size = 225761, upload-time = "2026-10-19T16:55:17.28Z" },
{ url = "https://files.pythonhosted.org/packages/f0/af/9a7c4b2e87f47a2a6a066acdc2157e9b7cb6222a224b95a432f54ae786ce/websockets-17.3-cp312-cp312-win32.whl", hash = "sha256:0824e6ecee838cc28e6481062574c7fde49f9a2cedfacd36f0e91cb4b37fd6fe", size = 218722, upload-time = "2026-10-19T16:55:18.658Z" },
{ url = "https://files.pythonhosted.org/packages/f7/e8/e52953a8280a1e51cb9307218443f68c8f4dc4b9952db6021d5c12d03c0f/websockets-17.3-cp312-cp312-win_amd64.whl", hash = "sha256:6644eecd99c4e8cdf72e646e17b391bb70b054f44becde37dcdc48cbec7e1527", size = 219024, upload-time = "2026-10-19T16:55:19.987Z" },
{ url = "https://files.pythonhosted.org/packages/85/a4/005eb7c1f5c1a249c1231a6ed83a54ada0b6b8ffab33a608ca20e3034ea0/websockets-17.3-cp312-cp312-win_arm64.whl", hash = "sha256:4a9133dbaedc6f1af358127275676171d9a1bb3e61f47ee62b2ff9acb78af3d0", size = 218950, upload-time = "2026-10-19T16:55:22.161Z" },
{ url = "https://files.pythonhosted.org/packages/ee/1d/c1f4a1c45c5ca2c35ae9fd7cac0e491f8a014c9a86933a5da7b04ff825cf/websockets-17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:52a2ca6f401fb49a8d5d9e0e48ade5f58f1bdda33132907b0e4e3b702628fa4e", size = 218243, upload-time = "2026-10-19T16:55:23.851Z" },
{ url = "https://files.pythonhosted.org/packages/4e/e8/f1b47d0ced4964ae748931a54347fd27b06b62e8ea7837a9de3aee0c5999/websockets-17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:80578e91764bfca297ddca322c00a3136d993b829e9839e7fa378a19c1fcc25d", size = 215927, upload-time = "2026-10-19T16:55:25.317Z" },
{ url = "https://files.pythonhosted.org/packages/16/df/ab0ed0445d8d81a133c8fed760f3b4f743a2f563804fdbecb2c32fe05d94/websockets-17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fe7a168d805c8a7bf1c2de55b7ab2bc958586bfd88d0e87235a92cac66b590a7", size = 216185, upload-time = "2026-10-19T16:55:26.678Z" },
{ url = "https://files.pythonhosted.org/packages/9f/51/b30fd6c5a33dac76a78083be125e3f9804f3d735da494ff03a46834f31f4/websockets-17.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:54fcd24944e6d127d198c28dc363967b7131ff0c97833962c2ef2048018de02d", size = 225574, upload-time = "2026-10-19T16:55:28.097Z" },
{ url = "https://files.pythonhosted.org/packages/1c/8a/8ede659919c0805d1fcc79c2ed16588d3419f1687c5117ba46f0217750ec/websockets-17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de2c12555bf05546f3ee75733f2c81de70aab7f530e86d0d2162b0ddcb3c39ae", size = 225854, upload-time = "2026-10-19T16:55:29.491Z" },
{ url = "https://files.pythonhosted.org/packages/a6/6a/e75d10e3896edc61320062890f71cdc0767dc4a54e2a26a943b703042d13/websockets-17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c95d25b5382ee0e6191ecafdeabefdc1f7bff8dfee29239b9b79e760ef3b04bd", size = 227091, upload-time = "2026-10-19T16:55:31.146Z" },
{ url = "https://files.pythonhosted.org/packages/dc/d3/658f1e153566e5edae02812122d9f020c30785d8b53c4bda61b8e976f77f/websockets-17.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8f244dfee05ea29b8064701880865eb1684f3a704a5665bd10b64694444dbbd4", size = 228526, upload-time = "2026-10-19T16:55:32.524Z" },
{ url = "https://files.pythonhosted.org/packages/28/00/002f95a02d721d8c6ec2676c5aee904a823838a9c205fc0b31eedd882a7b/websockets-17.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e99440ef446c7833690c1754e23b0188d440f3cb89812b0764c697a7880cb33", size = 227718, upload-time = "2026-10-19T16:55:34.085Z" },
{ url = "https://files.pythonhosted.org/packages/fa/9c/d36ec9499a62ec4e2a4eb570f93c6fcedd80ad5685e0f9d6866e8cfbbb84/websockets-17.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:26838e2d95d7004c52e8c7b6b51676c872093a3b55a497824f899116a15a8a55", size = 226472, upload-time = "2026-10-19T16:55:35.484Z" },
{ url = "https://files.pythonhosted.org/packages/36/5d/3a6d74e409f24ae4e8661a9d74d6645f71b3e91425a527c0e3f2e78bb15b/websockets-17.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:508775efa11d26aec186d10154359f52276e2baf9ca201b52356330883276dfd", size = 223710, upload-time = "2026-10-19T16:55:36.853Z" },
{ url = "https://files.pythonhosted.org/packages/e1/90/e08eb5d69b73cf5babf80922a8c964019b48339ce5c694a5d0c0f083c5a8/websockets-17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2789d203babb6ceb2770aadc2394c1f99263ec37fba57a93064f9a8ad899e8ff", size = 226532, upload-time = "2026-10-19T16:55:38.191Z" },
{ url = "https://files.pythonhosted.org/packages/c2/45/b1916efa1dad5af33beafe5c536810e1c80856b7e913f671c35031c57745/websockets-17.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cfef7f7aa434a1e632e0ebda14b39f4f1d7975e0fce5c77ea7b43156fabf9bd8", size = 225129, upload-time = "2026-10-19T16:55:39.635Z" },
{ url = "https://files.pythonhosted.org/packages/04/c1/d427abb3f11e92f102f70f8e73addb767fc62c614565c0ee3f5657fa3551/websockets-17.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:39f532fa0d9026de4b9315a175998646112776baad54d55348435c11b05d034e", size = 225900, upload-time = "2026-10-19T16:55:41.033Z" },
{ url = "https://files.pythonhosted.org/packages/07/2b/462fab8e4b09eaa4eac2f40fcff8ee30f851aa40fb518efb0177262f6590/websockets-17.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:85d6daa3a7ca404570ae130babbeb347ef9d3faca5300fb13cfa4bb48c3d30d1", size = 227007, upload-time = "2026-10-19T16:55:42.314Z" },
{ url = "https://files.pythonhosted.org/packages/71/36/7404a4376021a0cc7ae9fcbdb1196c0c8aa48df4fd370c185f4f5b677bff/websockets-17.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ce39283ef08b0f28478e920d4d89a1fc169176e568e8a6a821332673145522eb", size = 224565, upload-time = "2026-10-19T16:55:43.93Z" },
{ url = "https://files.pythonhosted.org/packages/c9/5f/b8ada5787d11e6edbf58e2d680235fb3b2fb3298991cc310566f4d6c2fbf/websockets-17.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:23046098ebf844753cfd211033624a8247520e5bf2194c24cb1a8c222917acff", size = 225516, upload-time = "2026-10-19T16:55:45.398Z" },
{ url = "https://files.pythonhosted.org/packages/23/1c/0533f760237ac6604efd0bf98c18ea4c1381f2cadd8c8d0b05faecb07a80/websockets-17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:724224d4c457207271239114bf43afafc3eca4a1b4661ccdd428d11869a03bbd", size = 225797, upload-time = "2026-10-19T16:55:46.989Z" },
{ url = "https://files.pythonhosted.org/packages/97/b4/0e580e8f96197130ebdb32b727e9c891594c930be30d8a9de9aa0bbb7e2c/websockets-17.3-cp313-cp313-win32.whl", hash = "sha256:5af7070d80d278832cbca9c3c3dd44b30a26dcfa3ae1e0f6f19ff34edc27b689", size = 218712, upload-time = "2026-10-19T16:55:48.379Z" },
{ url = "https://files.pythonhosted.org/packages/7d/6c/db5c907d1cbea84444bc759ff63db97fc586988d11a34d5565e8b50080fa/websockets-17.3-cp313-cp313-win_amd64.whl", hash = "sha256:66027e401c70d0cf55021b91b8a7ec2cd5810e20602f945b4fe6ae8b37ed232b", size = 219026, upload-time = "2026-10-19T16:55:50.688Z" },
{ url = "https://files.pythonhosted.org/packages/f1/d3/30f04bec5b9cf0c00c98c038494b210ece923a0b05b6df38a162b759e5ed/websockets-17.3-cp313-cp313-win_arm64.whl", hash = "sha256:6b14914e67044724c31a9360c7b89e9d070117c630746eefdc1620d967545acd", size = 218958, upload-time = "2026-10-19T16:55:52.085Z" },
{ url = "https://files.pythonhosted.org/packages/27/ad/f65e365bbf38d61164b0c2f64776fc7714bfa59ac51e492b038a35f4ab1b/websockets-17.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:bc8fd230ef013951900c1fda100eebe4a4b1b407dde2a44186995042dda2c628", size = 218288, upload-time = "2026-10-19T16:55:53.387Z" },
{ url = "https://files.pythonhosted.org/packages/48/1c/33271eba53b4927a1b91d99d838a6d7b888e6f708425262c911450d73696/websockets-17.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cdb8e4b0f6a41b857c74c03116e8ffbef59ef8e533e934b532843178990d01d5", size = 215977, upload-time = "2026-10-19T16:55:55.063Z" },
{ url = "https://files.pythonhosted.org/packages/3d/f3/6d982c60f6c7c95e5faed53d39263df273ecfda904c7d6920c37eee58dd1/websockets-17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:018fab26140ce221c3853454a608e4e7004f4f10641cb97819dcd5b917bb2e99", size = 216193, upload-time = "2026-10-19T16:55:56.424Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c6/4ff6b8d2a4b32719d4658bbd791ee9d19939a47a69b961c961fd9dc3a64c/websockets-17.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd0f3037d501178a761fd2814370aca547f79e99a200a61a1f6e7b79ec3d78b9", size = 225573, upload-time = "2026-10-19T16:55:58.252Z" },
{ url = "https://files.pythonhosted.org/packages/eb/8f/46e4030947f5590cb61832901f44a3edb07ef8d1e27f6c06b42c5f1c4d33/websockets-17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8dbb3862aa649de95660b6337d97101977f160b5b354d07b82fe309874d2c8bc", size = 225927, upload-time = "2026-10-19T16:55:59.561Z" },
{ url = "https://files.pythonhosted.org/packages/f0/1b/3db30fdf4fbefa4ac463060d3f87e2d444f960aa1234028468c09b88bffc/websockets-17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af37a1e6b6bd506b597d5fb4cf111fb527d1b2223608a5d441d4738827cfd166", size = 227169, upload-time = "2026-10-19T16:56:01.029Z" },
{ url = "https://files.pythonhosted.org/packages/02/ed/849799cfc0e3e359b75ca1d50605376da52dc9ef0b2a53931e3f5991f96e/websockets-17.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:740bba4223fa5216d4d6c5dcd29e697f37229b8a61b69e1a50527b0ea993ab2c", size = 228542, upload-time = "2026-10-19T16:56:02.377Z" },
{ url = "https://files.pythonhosted.org/packages/19/53/dcc397e20ce792fffd9a8f79b6333275748ee7a23f807e576166a0b16edd/websockets-17.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4170556762c6fd2913d99dfdd8dbb4c927584899df7facb1034627e355263c45", size = 227772, upload-time = "2026-10-19T16:56:03.768Z" },
{ url = "https://files.pythonhosted.org/packages/c9/36/e5935782f71e9e22247b2a2e562da05e18d8945c49f2692ca1bdaceec178/websockets-17.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d355b22270be8062360b9df2ee0b10277590789a1059257916fb9e256a0cff80", size = 226518, upload-time = "2026-10-19T16:56:05.332Z" },
{ url = "https://files.pythonhosted.org/packages/27/76/bd5978a1e403fc646230cf35cf7c06b40b3a232d440881b9e89ede229b18/websockets-17.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4aa85b34c21cf97b3470793d0bf0763d32bfd1e67b83067a0a3ba8dc3474dacf", size = 223766, upload-time = "2026-10-19T16:56:06.703Z" },
{ url = "https://files.pythonhosted.org/packages/da/87/c66eb39bd851d5c14d9eb75aa64d03cfcf2ded77927ca067b5917291d19d/websockets-17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a0e2e7c2c1a4f25db7b6596738d810c10732e77f852247300a1bc49e364d6dc9", size = 226644, upload-time = "2026-10-19T16:56:08.198Z" },
{ url = "https://files.pythonhosted.org/packages/fe/ae/b9a31d5497fa0b73629a097b173a26123da2ed656b562114644bc623414b/websockets-17.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7fa69212ea60ea24e2d8aff23fe102b1f87f1b27a320f92d2e7b3468997a496a", size = 225107, upload-time = "2026-10-19T16:56:09.627Z" },
{ url = "https://files.pythonhosted.org/packages/61/0c/b773a102f84b60b2052b2cfde773d2f3cd3e8eaa93a145f54450460549cf/websockets-17.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:80a6e37e235780f189558ab804f3cbc01c18f5c578995739b49613d544d34ebf", size = 225891, upload-time = "2026-10-19T16:56:10.973Z" },
{ url = "https://files.pythonhosted.org/packages/b2/fa/e7e538575dd4912dea9ea4c9ae86519aa1420969d9cb5cf02fd10fd20ba7/websockets-17.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:628efe13630ac3397e7d1278b6be6f22f37a86d638a324334b4cd43c1daf9928", size = 227064, upload-time = "2026-10-19T16:56:12.295Z" },
{ url = "https://files.pythonhosted.org/packages/28/87/bf2b928c156deefd0c50285a48fc985e2ca85d284e7d9981cc5b79b0e696/websockets-17.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:443fa28652682d2a3ec3eca49af1b87cb2523561257683e62e03a98967b22138", size = 224618, upload-time = "2026-10-19T16:56:13.73Z" },
{ url = "https://files.pythonhosted.org/packages/8c/9a/d59817ba3535f787c1cf8990a01b3d2896a7d9d8f3b5d55bed22fce595a6/websockets-17.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6705af9d1005127302c04d9f1533ba56e289336c465923ef878430e99295a0b9", size = 225571, upload-time = "2026-10-19T16:56:15.199Z" },
{ url = "https://files.pythonhosted.org/packages/a6/96/8210379dac889e16c75099aefffa200dc55dc8cb030010f309a7eb9c75c1/websockets-17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9bde5d82d7ac4859e0cb3a35e148f537a8d4e56aa43f42582b5f263982f095ae", size = 225869, upload-time = "2026-10-19T16:56:16.569Z" },
{ url = "https://files.pythonhosted.org/packages/97/0b/d4741c977fec9e6482beffece572d544d42d230f56fc3ef504f96521063a/websockets-17.3-cp314-cp314-win32.whl", hash = "sha256:be7be57097a65a8fab3d0f0a10d12931deb626a7d8082eb732b1ae41f3a202e3", size = 218232, upload-time = "2026-10-19T16:56:18.028Z" },
{ url = "https://files.pythonhosted.org/packages/33/69/2f9ce899776d0c522f7f44080cad4228a1c14ec13b0125a2efc12ebe41c9/websockets-17.3-cp314-cp314-win_amd64.whl", hash = "sha256:2b06213c8e1ce93b18d0476d7a5e647436c8f23d099888c9065e930e80f4bed5", size = 218582, upload-time = "2026-10-19T16:56:19.399Z" },
{ url = "https://files.pythonhosted.org/packages/4e/e9/11b885666d72add674440a3435c059bea325616959cec8be42605948714c/websockets-17.3-cp314-cp314-win_arm64.whl", hash = "sha256:6b7b540bb321f7f0a7b8f5d107f941bb361e3a154c0b594dae0394997b91b743", size = 218465, upload-time = "2026-10-19T16:56:21.005Z" },
{ url = "https://files.pythonhosted.org/packages/c4/0e/0c0f40ab8eefe9d5a6c78b4e267d8e37d70d3db8dd195574e0ab867e4f81/websockets-17.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c9d06edfd974e918b40e0662a7e76dac13125529ee5ec9fbe42dc0d6faa209c3", size = 218614, upload-time = "2026-10-19T16:56:22.457Z" },
{ url = "https://files.pythonhosted.org/packages/1f/93/d22b3d63aa439bc01a9477880070d05e0b257b0548294786a33be3e842ec/websockets-17.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a0bcf66289b9d017c175562c35c5304b5cc3ed56aded994ae30b0a9654ba405", size = 216151, upload-time = "2026-10-19T16:56:23.95Z" },
{ url = "https://files.pythonhosted.org/packages/b9/92/f60a9672cdeb8cb4f7156e6e83c76d59fdc586737a97ac77129e9fc188a2/websockets-17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f3ed747b7651668131029c720fbc32c7ee2d129bdad200e6c8f4037b20f7913a", size = 216354, upload-time = "2026-10-19T16:56:25.251Z" },
{ url = "https://files.pythonhosted.org/packages/9d/da/2be8c5f3c6612d4523f95b074e2352bc11bd090cf121d32bc11af1d52e70/websockets-17.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5868a02c4f6d698ddd19b4c9742748c03dec045289915806845189d96bc3d314", size = 225937, upload-time = "2026-10-19T16:56:26.726Z" },
{ url = "https://files.pythonhosted.org/packages/85/1c/cb84aec871840acd66dfa84520bf7b8e48ce70bdb3f4baa4efd097fbe6b1/websockets-17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa87ea2ad64e59fe5622c1dc5716106b6d6e7e4b26b848cb5f5710a75c292ef5", size = 226215, upload-time = "2026-10-19T16:56:28.1Z" },
{ url = "https://files.pythonhosted.org/packages/9b/7b/cfb65557accec2eda2c80288e13eca6f1c797c579375906eb73f4053cfd1/websockets-17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65e937576141ac50fac32fa9736dbb3e0e4a7769f91be004011a09216c6ab05f", size = 227389, upload-time = "2026-10-19T16:56:29.518Z" },
{ url = "https://files.pythonhosted.org/packages/70/a3/7e08ec4b23e376ed228831aa44b2f2a3f2e7025cc66dda4b2e53dcc2619e/websockets-17.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:35381b575f70a08d4241e73c0cde4672bb9b2a9aae8767ca6b7fb61305a052c8", size = 229534, upload-time = "2026-10-19T16:56:31.072Z" },
{ url = "https://files.pythonhosted.org/packages/4b/7d/8a813e3e53956f876cfaad4f6358625bbd94168aa8046c44e2f3561f12af/websockets-17.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5142798871a248b422f353e0684d51dfa119de3403a6fa0904b7cb806d9790ce", size = 227959, upload-time = "2026-10-19T16:56:32.656Z" },
{ url = "https://files.pythonhosted.org/packages/41/23/5f3dab6ab44f2f30042db83a658a57129d5e3c0425c798b3c3e727bc060a/websockets-17.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e164c4a6868d11988c7f2386e3278ccff21c665cd7d08279b62709dd37a3167c", size = 226691, upload-time = "2026-10-19T16:56:34.101Z" },
{ url = "https://files.pythonhosted.org/packages/5b/49/99a3fb5a2e94d677fbd0d014a6f9a56a3d2b8e8007b7175727bc4fc4ae94/websockets-17.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10b24e836ee0233ca078e1244f6ba40343a22127a9a736f46a7e7b18de2cc711", size = 224225, upload-time = "2026-10-19T16:56:35.452Z" },
{ url = "https://files.pythonhosted.org/packages/6f/e5/f503a4d757858d083c90ea85dd1fd636ae9eb63c9552c75a4c38514ee7ed/websockets-17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:56a76556ab04ab54a675acce2cac8b4e74ea2cec558a7c9ed37e0367b04b5d50", size = 226984, upload-time = "2026-10-19T16:56:36.887Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a6/8586844ad4b21988837624e1f4b331bc71919c85a6eb1ee3c8bd29893f27/websockets-17.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:33221e49be79d7bfe6d12bc52ef9458b256ab74fa8ac5660acfa3065de1c7870", size = 225436, upload-time = "2026-10-19T16:56:38.213Z" },
{ url = "https://files.pythonhosted.org/packages/a7/b9/f419506494d6ffd205a9c3258f56ff5aeae6976de26361057de74f4ab659/websockets-17.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2fa79b9a48ae59744316c71a34cb9f76f30c93e117338522c5135cb7eac10351", size = 226325, upload-time = "2026-10-19T16:56:39.606Z" },
{ url = "https://files.pythonhosted.org/packages/28/c5/55a58faa67149e84cb98b33f9d8fda95242677ea07b78f3ecaf2e77877d6/websockets-17.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f87f02afe05c1c246012e3de100a77cfbabd17db709fe93cf074271ab239b312", size = 227342, upload-time = "2026-10-19T16:56:41.041Z" },
{ url = "https://files.pythonhosted.org/packages/35/e0/fbe561dff24b6ef1b2034877f37b56eb9b81b3c103e2a8b03819bf2b3b6d/websockets-17.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8135a549e27e9941a70e2d2156676ef2ffeff4b06e9d564dbaaae963482afdef", size = 225036, upload-time = "2026-10-19T16:56:42.433Z" },
{ url = "https://files.pythonhosted.org/packages/0d/37/d80753064f8370a24d3bec74807c3928d7b0ebef5381956e2a0108b365c2/websockets-17.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ace8dd1efa5f44c482d091aa38322e20e3ffe5adcdba6b67ad9f4e7da58f3f68", size = 225807, upload-time = "2026-10-19T16:56:43.774Z" },
{ url = "https://files.pythonhosted.org/packages/f1/9d/a11d3168522a2959c4a5661728744d62b9eee344abdba5f9fbc1d359742f/websockets-17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2129c9a5fc05a230ce088745a790af073e582b584badc7b05db067ee326f56d7", size = 226241, upload-time = "2026-10-19T16:56:45.088Z" },
{ url = "https://files.pythonhosted.org/packages/06/28/16107b3b83fd2b5024bfecf8d000140f555204a0191202f9c4c981137810/websockets-17.3-cp314-cp314t-win32.whl", hash = "sha256:af7c474f0d8f4d2a196370e85033873c7b063a0b5da9a4f143aa023e3bd25407", size = 218365, upload-time = "2026-10-19T16:56:46.41Z" },
{ url = "https://files.pythonhosted.org/packages/77/dd/8b00e1dd552788d8c7762d7ef61de8f2f238d7357b1a02d4c4650b112196/websockets-17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c63ad657695e8f1a48624754cb6b464118ac50d4c1d477c26461c98390f0a62", size = 218706, upload-time = "2026-10-19T16:56:47.754Z" },
{ url = "https://files.pythonhosted.org/packages/39/eb/5d90d57a5cbe614b2f13433aaf1c0b068c804589379061ba06812a185a89/websockets-17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:3c4f631dc8d00e470cde3ec0a35d4b58a817bea592411956f1cf4099dbb306b3", size = 218622, upload-time = "2026-10-19T16:56:49.07Z" },
{ url = "https://files.pythonhosted.org/packages/6e/41/1efe5ecae1f32495dc75641704f959bd2d8e56cdc825f3acc5e010a51960/websockets-17.3-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f300f28dff74ff4148c21e6971e4de6ca0c34e0086a3d95c017c5174ebf85ca5", size = 218211, upload-time = "2026-10-19T16:56:50.423Z" },
{ url = "https://files.pythonhosted.org/packages/01/ec/4edb653533241203db3f11522e122338d4fda50a41ce2a542029086f8eda/websockets-17.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:32029441af953e980b9940f042b337fe4ad9a97fa8b17a4ed878ec67b5b89ef4", size = 215944, upload-time = "2026-10-19T16:56:51.766Z" },
{ url = "https://files.pythonhosted.org/packages/d5/b9/ec450b9650925dd5cf27c1f36d0464786fe3ac702ed656879c4c0dd5c4df/websockets-17.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09f007b2150eff015b2109f006e2ac2012d376f41486defa88ae78a776b00cff", size = 216164, upload-time = "2026-10-19T16:56:53.125Z" },
{ url = "https://files.pythonhosted.org/packages/15/1e/63f7fbff9f0540a0331f1a62424c9ff24faa6ba8aec264f55e9e31017eea/websockets-17.3-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bdc658c9474864893e0b6b94f1390bf6a912952df1870dd93bb7d1a902b5628", size = 225608, upload-time = "2026-10-19T16:56:54.442Z" },
{ url = "https://files.pythonhosted.org/packages/0c/1b/f6679ad61cb8c110033a1b30d561d5c1052983e202157251ff1778f27932/websockets-17.3-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15decc0fe293d9e3f924bd483f3d76f3d0ff26845112a470e3f8842fec2c82a1", size = 226042, upload-time = "2026-10-19T16:56:55.727Z" },
{ url = "https://files.pythonhosted.org/packages/28/c6/ed993aaef8046cbbb939ae367ee3dd5383dbd055f0500a5b8aed101168b6/websockets-17.3-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:997617dd7856c5dad742bf6893b872319156dd43f47587541a59e4a852d52bbc", size = 227203, upload-time = "2026-10-19T16:56:57.076Z" },
{ url = "https://files.pythonhosted.org/packages/aa/96/7057dc71affe6c8252df8648f5a225642228a6fdd8d046a6b9aa1fb0e9eb/websockets-17.3-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a004c1fe5a72c496f28c6c1d8c2dc5cfe3278d99478e18f8b838c70a912b366f", size = 228900, upload-time = "2026-10-19T16:56:58.633Z" },
{ url = "https://files.pythonhosted.org/packages/43/e3/8b3268bb7e72b465e55172b86d55f58aa277fb6d9bac92aee615345874df/websockets-17.3-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35fe35d20baa661cef4a12ee7bc42eaf2376d43ef9816ab29dd47ca957f7a73a", size = 227684, upload-time = "2026-10-19T16:56:59.975Z" },
{ url = "https://files.pythonhosted.org/packages/59/15/fa31c0a12240b1225c3712ff080d2f7ede4f695ca90e59fdb58e8affabba/websockets-17.3-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8b3bede7d583bc9f47d8693a29984882e59077db1a5238ad977fc70ae8169741", size = 226580, upload-time = "2026-10-19T16:57:01.371Z" },
{ url = "https://files.pythonhosted.org/packages/22/b5/75d911c206a3035e2ecfad107878e62c06e574b2437fa2cfb4ecef436fa3/websockets-17.3-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dafbb58a149d1ebd5767b3f064f911c5ee0897608005e51c3904b0ada2a8d99c", size = 223967, upload-time = "2026-10-19T16:57:02.839Z" },
{ url = "https://files.pythonhosted.org/packages/0e/f8/f98aa36f611acd5770819f6690125c20d55f221fd6db8d00d284b5f78e89/websockets-17.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:80326bc571164bbbb6bead2e35d0c943b23b24fde629032197c58151529d24d8", size = 226689, upload-time = "2026-10-19T16:57:04.458Z" },
{ url = "https://files.pythonhosted.org/packages/1a/36/d52f5dff7597de6fbfc26c3d126abd668137915719aebf97abcc3be77eca/websockets-17.3-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:7d53fc4b90ef6e11de639dc18744d9be8cbd70ccb5ce80027aaffe7b1ab0215a", size = 225353, upload-time = "2026-10-19T16:57:05.857Z" },
{ url = "https://files.pythonhosted.org/packages/d4/10/dcd70ad9f9cd20e8af43284ac250af610e8bf9022da25f5b409153d033b4/websockets-17.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:fdd00937f15f5f93c8e53d8060a8c4c010431ab595a9b5b50ddd569a04603944", size = 225879, upload-time = "2026-10-19T16:57:07.249Z" },
{ url = "https://files.pythonhosted.org/packages/70/ab/f26c6bfa99d38ca3d1bf23145306d97c5d0c75e133a8d837373e4b3d4629/websockets-17.3-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1dae5cd6c92d4440b15f20aa2987baa73c47ea14baa0fec8536a542e82e0ab33", size = 227031, upload-time = "2026-10-19T16:57:08.817Z" },
{ url = "https://files.pythonhosted.org/packages/d7/3f/c8fd74cdb526cc65295b7468f2f84ba16678646d8174288822f99f510274/websockets-17.3-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:b225c656efa2dedecc81651d2290170af25887b7de4f38c5efc7c5ae2b9442ab", size = 224842, upload-time = "2026-10-19T16:57:10.298Z" },
{ url = "https://files.pythonhosted.org/packages/c4/c5/b3a07ce1c54fc0f5441642f5352662b90d7e658c966730eaf6d78a5f20bc/websockets-17.3-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:bd645b5d2f810a181c44f4a71135e7c365b44f4be9ed3d1892635be13a857553", size = 225744, upload-time = "2026-10-19T16:57:11.615Z" },
{ url = "https://files.pythonhosted.org/packages/66/37/8321a8a6ab0215d4df0238e49ace5f539364cf337bb122b85c788e8c93ec/websockets-17.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d2605a3b2af8d66f8b75312d223642c065ec773446c04566d9030ad5e2db13c9", size = 226006, upload-time = "2026-10-19T16:57:13.049Z" },
{ url = "https://files.pythonhosted.org/packages/f8/04/8c457a7e6a260b673bf4e6f2f422eb4af5f11c48505748359706f24f1bef/websockets-17.3-cp315-cp315-win32.whl", hash = "sha256:82e60c00cfb8493a5ba27cdf9b3695fdd153f666f73e11f18968f78ead1d8705", size = 218196, upload-time = "2026-10-19T16:57:14.41Z" },
{ url = "https://files.pythonhosted.org/packages/a0/7c/c2df129d9e344b99bb388bcac54fbbdfeec29e6c716c12dc9a3398757940/websockets-17.3-cp315-cp315-win_amd64.whl", hash = "sha256:4d5f2492cb2192aab2d42e57f2caa2159b4fd20fde06f859ec13354f524b176c", size = 218490, upload-time = "2026-10-19T16:57:16.234Z" },
{ url = "https://files.pythonhosted.org/packages/e6/4f/721b778c85559b2949e46246838e31a346dd815b3cdd8460fe517235675a/websockets-17.3-cp315-cp315-win_arm64.whl", hash = "sha256:55494475206113dbaf731dc8c5ffd5e1094175b753b7bbaad09fe3d7df61165c", size = 218403, upload-time = "2026-10-19T16:57:17.631Z" },
{ url = "https://files.pythonhosted.org/packages/b3/28/241d9ebb4dad79346b5a6abf9f14d7685b12ca358d250eea4920cdb1caf3/websockets-17.3-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:23cb6f6d64e3a389bb8ba150f947f9a6c8c0aca383e814ca3c5cffab738d5646", size = 218523, upload-time = "2026-10-19T16:57:18.978Z" },
{ url = "https://files.pythonhosted.org/packages/b0/bb/559fe539992102c66cbca38b488af6c7b184ab7736d819afc0f72268a2b4/websockets-17.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f27e7e8a85512b6a4b490d5756edde8e6772883a5800e1476172e44eda3f7d7c", size = 216103, upload-time = "2026-10-19T16:57:20.412Z" },
{ url = "https://files.pythonhosted.org/packages/34/ee/e8ade4f13a3d0da68636fe44bb8ac1a3e36fc57070512788320dead42067/websockets-17.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:6342f79d6f50e680c8aa59e0615d745e867e51ffd59c77093be60b7c433067af", size = 216316, upload-time = "2026-10-19T16:57:21.761Z" },
{ url = "https://files.pythonhosted.org/packages/4d/06/4812b2a7a5e6571d9be425e76cf54ee69aaa15720caa02307785aa5b19a7/websockets-17.3-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fc23e72bd7f8a8e540fed2244524d0a9aeb6ae3325f3d56f04a7a3debe5b04c2", size = 226467, upload-time = "2026-10-19T16:57:23.077Z" },
{ url = "https://files.pythonhosted.org/packages/d3/e8/adfc05d7f9b6b0cce6cc554426c50b7058609119b6b84a39171fe5ed4e50/websockets-17.3-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:722ab747ff5dc28070b1a8c020dba6466f13f2ab56bc251ad301fefa2454aa1e", size = 226736, upload-time = "2026-10-19T16:57:24.419Z" },
{ url = "https://files.pythonhosted.org/packages/96/b2/d5b93dd933552554507edae9e3a4890269a8ea8b46019450ea9c8a30e7cc/websockets-17.3-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d20d6ea5aabbeba2e054d33e405192ef75dc12947bbd00ec97d23b14969d325", size = 228065, upload-time = "2026-10-19T16:57:25.908Z" },
{ url = "https://files.pythonhosted.org/packages/10/37/642f0f1cf1c4814147a4f0586f796a0b38e42b69e5a296bfc270fb660051/websockets-17.3-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:90cc9973c51811918247a78d9ab2f54871296e4e37e69a98aba4d3c9acedbf62", size = 229692, upload-time = "2026-10-19T16:57:27.63Z" },
{ url = "https://files.pythonhosted.org/packages/5b/33/eac81cf62ac394b61db6a27ccbe8fb9ae382016ef73ede1edf9242a6a43e/websockets-17.3-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63b87c93e28957e6dc2bb92d31727a863c9bf4a4daa080f0995fdcb0242c9166", size = 228503, upload-time = "2026-10-19T16:57:29.031Z" },
{ url = "https://files.pythonhosted.org/packages/0d/84/e057788a9bd056f4f7d3a06b59efaaf724bbc6f2d1e1d316f8a93c951abc/websockets-17.3-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f7431e71072962d3ed1768038200aa4f1e76d9c2d7bedd554d57a55faaf6644", size = 227379, upload-time = "2026-10-19T16:57:30.571Z" },
{ url = "https://files.pythonhosted.org/packages/fa/a7/cca1f9f45b893bb5b7545f2b234f910e07fcd2f8065724ac1a62a0961be2/websockets-17.3-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:534ed583fd4323de45d196535a444cebd1af2105e6e4e0cdcbf8cea266a8747a", size = 224719, upload-time = "2026-10-19T16:57:31.895Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a0/5d07451027150c802ce4c9999e86994a3f0de5f4dbe3d1cce46995999269/websockets-17.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8879b6a4040ee925c638548238ab7fe612cf5fa8752fe847346da0da8be81c3f", size = 227564, upload-time = "2026-10-19T16:57:33.236Z" },
{ url = "https://files.pythonhosted.org/packages/62/90/835f37e13fac0c615a700caae0d39deff1de2d2b9b341836a786f9b359e0/websockets-17.3-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58e5d1e935a90878a935c7d2737a25c71cf4f1c13b6e6bef454ab722c4f338bb", size = 226218, upload-time = "2026-10-19T16:57:34.876Z" },
{ url = "https://files.pythonhosted.org/packages/16/8a/718639939b947fe715501e8073350fd3f8a42f8b5eba3c0c2389c5836513/websockets-17.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:0a657b51e4a5f4ac577c11e706ff9a884ea5aaf3e2666bae283a251c63e66acb", size = 226712, upload-time = "2026-10-19T16:57:36.248Z" },
{ url = "https://files.pythonhosted.org/packages/e8/45/6bef8572b9b0dee51369b69776af1e4c7cc3a807f199dc3c165bbcf1199a/websockets-17.3-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:411aa0fb234f42a1444dea1e177890ddfc577b36ffdfafec25cb0d2c818e1af8", size = 227819, upload-time = "2026-10-19T16:57:37.586Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3f/3b726d0b46a132e641d2d8c0243ea8a7d39464dbaf28c7a206e51fbae681/websockets-17.3-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:623d4b44f64efdf235b1e8dce266057073d93cde6197e7d7684f20c297f67f30", size = 225573, upload-time = "2026-10-19T16:57:39.285Z" },
{ url = "https://files.pythonhosted.org/packages/90/96/cd972a12e1412d9b42c42ef42efbd81aa396ca9408d982d524012f011a8d/websockets-17.3-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5d0c871efc696ef5eb8514df64b86ab28736db753ebb42a2101f3b562f4849c0", size = 226430, upload-time = "2026-10-19T16:57:40.915Z" },
{ url = "https://files.pythonhosted.org/packages/7d/2d/e11524771588188a39f6da234779d4942496b96c5a10b92f3df30db48608/websockets-17.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1db2604e6aedb7a2cb2f6e0575d6c9a852ccac8239703a930ac83b63309177a5", size = 226728, upload-time = "2026-10-19T16:57:42.245Z" },
{ url = "https://files.pythonhosted.org/packages/17/9b/63722aed973f53ea579e1ce0c4563a2b8b536629e556feff26e4c6a2a60d/websockets-17.3-cp315-cp315t-win32.whl", hash = "sha256:07246d4f0293ef0b87eff3d7dd88c1f03dd9f678fa39f6de87b2af8504e7e0de", size = 218325, upload-time = "2026-10-19T16:57:43.585Z" },
{ url = "https://files.pythonhosted.org/packages/34/07/5e0c92e225546f1789c957c0383852797be7c899b07207e9b6eb4a93473f/websockets-17.3-cp315-cp315t-win_amd64.whl", hash = "sha256:856bbb24f2ee6436af7609267e73309e4bb0a0f9982f79f1c2e01019279c6671", size = 218641, upload-time = "2026-10-19T16:57:44.918Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e4/339663de65c44ab3eb5209890e86eed26e1a131d6afab3df6f1c3e5b4d8c/websockets-17.3-cp315-cp315t-win_arm64.whl", hash = "sha256:a22c4956a4d858200dba83f0691bb692d77b0eeddfca80157820d20b85dea4f1", size = 218564, upload-time = "2026-10-19T16:57:46.332Z" },
{ url = "https://files.pythonhosted.org/packages/5a/1a/5582103e0362f86c4725051ded89e585ceaf750f26beace530b7e42c81c0/websockets-17.3-pp312-pypy312_pp80-macosx_10_15_x86_64.whl", hash = "sha256:0e65260a1b1d7b81651f47343d836de70b88b02358ae3658efa6382104ec37b1", size = 215872, upload-time = "2026-10-19T16:57:55.95Z" },
{ url = "https://files.pythonhosted.org/packages/ef/a7/f69d65ed3ff9ae73d428a918300c23a992e4d50683090b8dc33cc3341d5a/websockets-17.3-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:94c02b0702656f083c098bdbd93bce22a7aa7a2b34ba56d2472efb67dab7cf58", size = 216136, upload-time = "2026-10-19T16:57:57.251Z" },
{ url = "https://files.pythonhosted.org/packages/69/76/c64fda4ac18a3aeb32e66acec481011a766add480b6362fad80e2c3c1963/websockets-17.3-pp312-pypy312_pp80-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ade76f8c84ade325bbed96806357fffd353fcc29a5166d6dc6d39381f6d9f76c", size = 217080, upload-time = "2026-10-19T16:57:58.593Z" },
{ url = "https://files.pythonhosted.org/packages/6b/6d/085a3f0e6152bbe79917973f3d33cd008437de6c04675e2f15253b921f21/websockets-17.3-pp312-pypy312_pp80-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a215749c9a88ef2842e880215193e56ae60b856773c99702b078236bc6d4adb", size = 216966, upload-time = "2026-10-19T16:57:59.924Z" },
{ url = "https://files.pythonhosted.org/packages/ed/b8/7eb904583ea2090b329f50487e6f8dc58eb558712b9f3c16f289f0be668e/websockets-17.3-pp312-pypy312_pp80-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7b5d23ef144b32645d97bd99f382c4394c443395f45fe1576786838d26e308", size = 217828, upload-time = "2026-10-19T16:58:01.822Z" },
{ url = "https://files.pythonhosted.org/packages/79/36/f54b4a1cd9456ca0a65a76d77d181621433793aca633dfeed214748ff4f0/websockets-17.3-pp312-pypy312_pp80-win_amd64.whl", hash = "sha256:13eace1b315345ebac5b27821f5dcce24e5cf9976ab18f79e00a59bdd67e5d19", size = 306301, upload-time = "2026-10-19T16:58:03.161Z" },
{ url = "https://files.pythonhosted.org/packages/d7/b0/3f01fcb3406662f6deb1e3e72b34dd0979e993991c0b7435a9e12bc14f02/websockets-17.3-py3-none-any.whl", hash = "sha256:dc2b495a92b3971966bf102e73589f950db3ec1ca6713265355ce7771d97b199", size = 212375, upload-time = "2026-10-19T16:58:04.483Z" },
]
[[package]]
+7
View File
@@ -86,6 +86,13 @@ def get_frontend_configuration(request):
"enable_firefox_proxy_workaround": settings.LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND,
"default_sources": settings.LIVEKIT_DEFAULT_SOURCES,
"default_video_codec": settings.LIVEKIT_DEFAULT_VIDEO_CODEC,
"reconnect": {
"backoff_enabled": settings.LIVEKIT_RECONNECT_BACKOFF_ENABLED,
"base_delay_ms": settings.LIVEKIT_RECONNECT_BASE_DELAY_MS,
"max_delay_ms": settings.LIVEKIT_RECONNECT_MAX_DELAY_MS,
"give_up_after_ms": settings.LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS,
"backoff_reset_ms": settings.LIVEKIT_RECONNECT_BACKOFF_RESET_MS,
},
},
"allow_unregistered_rooms": settings.ALLOW_UNREGISTERED_ROOMS,
"authenticated_users_can_edit_display_name": (
+2 -3
View File
@@ -137,7 +137,7 @@ class CanMuteParticipant(permissions.BasePermission):
- Admins and owners can always mute.
- When `everyone_can_mute` is enabled on the room, any participant
currently in the room (proven by a valid LiveKit token for that room)
can mute.
can mute. It is enabled unless explicitly set to `False`.
"""
def has_object_permission(self, request, view, obj):
@@ -149,8 +149,7 @@ class CanMuteParticipant(permissions.BasePermission):
if not is_livekit_token_auth and obj.is_administrator_or_owner(request.user):
return True
everyone_can_mute = obj.configuration.get("everyone_can_mute", True)
if not everyone_can_mute:
if obj.configuration.get("everyone_can_mute") is False:
return False
if not is_livekit_token_auth:
+29 -27
View File
@@ -27,9 +27,12 @@ from timezone_field.rest_framework import TimeZoneSerializerField
from core import models, utils
from core.api.exceptions import ensure_room_not_deleted
from core.services.lobby import LobbyService
logger = logging.getLogger(__name__)
DISPLAY_NAME_MAX_LENGTH = 255
class UserSerializer(serializers.ModelSerializer):
"""Serialize users."""
@@ -170,17 +173,18 @@ class RoomSerializer(serializers.ModelSerializer):
return value
def to_representation(self, instance):
"""
Add users only for administrator users.
Add LiveKit credentials for public instance or related users/groups
"""
"""Add accesses only for administrator and owner users."""
output = super().to_representation(instance)
request = self.context.get("request")
if not request:
return output
role = instance.get_role(request.user)
if "role" in self.context:
role = self.context["role"]
else:
role = instance.get_role(request.user)
is_admin_or_owner = models.RoleChoices.check_administrator_role(
role
) or models.RoleChoices.check_owner_role(role)
@@ -193,26 +197,9 @@ class RoomSerializer(serializers.ModelSerializer):
)
output["accesses"] = access_serializer.data
should_access_room = (
(
instance.access_level == models.RoomAccessLevel.TRUSTED
and request.user.is_authenticated
)
or role is not None
or instance.is_public
)
if should_access_room:
room_id = f"{instance.id!s}"
username = request.query_params.get("username", None)
output["livekit"] = utils.generate_livekit_config(
room_id=room_id,
user=request.user,
username=username,
configuration=output["configuration"],
role=role,
)
else:
if not LobbyService.can_bypass_lobby(
room=instance, user=request.user, role=role
):
del output["pin_code"]
return output
@@ -355,10 +342,23 @@ class StartRecordingSerializer(BaseValidationOnlySerializer):
return value
class JoinRoomSerializer(BaseValidationOnlySerializer):
"""Validate the data sent to join a room."""
username = serializers.CharField(
required=False,
allow_blank=True,
allow_null=True,
max_length=DISPLAY_NAME_MAX_LENGTH,
help_text="Display name in the room. The default name is used when it "
"is missing, null or blank.",
)
class RequestEntrySerializer(BaseValidationOnlySerializer):
"""Validate request entry data."""
username = serializers.CharField(required=True)
username = serializers.CharField(required=True, max_length=DISPLAY_NAME_MAX_LENGTH)
participant_id = serializers.CharField(
required=False, allow_null=True, max_length=128
)
@@ -675,7 +675,9 @@ class RaiseHandSerializer(BaseValidationOnlySerializer):
class RenameParticipantSerializer(BaseValidationOnlySerializer):
"""Serializer for renaming a participant in a room."""
name = serializers.CharField(min_length=1, max_length=255, allow_blank=False)
name = serializers.CharField(
min_length=1, max_length=DISPLAY_NAME_MAX_LENGTH, allow_blank=False
)
class ExternalProcessEventSerializer(BaseValidationOnlySerializer):
+73 -24
View File
@@ -309,35 +309,35 @@ class RoomViewSet( # pylint: disable=too-many-public-methods
ensure_room_not_deleted(obj)
return obj
def _get_object_or_unregistered_slug(self):
"""Return the requested room, or the slug of an unregistered room."""
try:
return self.get_object(), None
except Http404:
slug = slugify(self.kwargs["pk"])
if not settings.ALLOW_UNREGISTERED_ROOMS or not slug or utils.is_uuid(slug):
raise
return None, slug
@staticmethod
def _get_unregistered_room_data(slug):
"""Describe an unregistered room, which only exists by its slug."""
return {
"id": None,
"slug": slug,
"access_level": RoomAccessLevel.PUBLIC,
}
def retrieve(self, request, *args, **kwargs):
"""
Allow unregistered rooms when activated.
For unregistered rooms we only return a null id and the livekit room and token.
LiveKit credentials are never returned: they are obtained by joining.
"""
try:
instance = self.get_object()
except Http404:
if not settings.ALLOW_UNREGISTERED_ROOMS:
raise
slug = slugify(self.kwargs["pk"])
username = request.query_params.get("username", None)
data = {
"id": None,
"slug": slug,
"is_administrable": False,
"access_level": RoomAccessLevel.PUBLIC,
"livekit": {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": slug,
"token": utils.generate_token(
room=slug, user=request.user, username=username
),
},
}
else:
data = self.get_serializer(instance).data
room, slug = self._get_object_or_unregistered_slug()
if room is None:
return drf_response.Response(self._get_unregistered_room_data(slug))
return drf_response.Response(data)
return drf_response.Response(self.get_serializer(room).data)
def list(self, request, *args, **kwargs):
"""Limit listed rooms to the ones related to the authenticated user."""
@@ -572,6 +572,55 @@ class RoomViewSet( # pylint: disable=too-many-public-methods
{"message": f"Recording stopped for room {room.slug}."}
)
@decorators.action(
detail=True,
methods=["post"],
url_path="join",
permission_classes=[],
)
def join(self, request, pk=None): # pylint: disable=unused-argument
"""
Join a room, getting LiveKit credentials when allowed to enter directly.
The response contains the room with a livekit object holding the url,
room and token to connect with when the requester may enter without
going through the lobby. Otherwise, it has no livekit object and the
client should request entry through the lobby.
Unregistered rooms, when allowed, are always joined directly.
"""
serializer = serializers.JoinRoomSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
username = serializer.validated_data.get("username") or None
room, slug = self._get_object_or_unregistered_slug()
if room is None:
return drf_response.Response(
{
**self._get_unregistered_room_data(slug),
"livekit": utils.generate_livekit_config(
room_id=slug, user=request.user, username=username
),
}
)
role = room.get_role(request.user)
data = self.get_serializer(
room, context={**self.get_serializer_context(), "role": role}
).data
if LobbyService.can_bypass_lobby(room=room, user=request.user, role=role):
data["livekit"] = utils.generate_livekit_config(
room_id=str(room.id),
user=request.user,
username=username,
configuration=data["configuration"],
role=role,
)
return drf_response.Response(data)
@decorators.action(
detail=True,
methods=["post"],
@@ -0,0 +1,18 @@
# Generated by Django 5.2.17 on 2026-10-21 20:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0028_room_deleted_at'),
]
operations = [
migrations.AlterField(
model_name='application',
name='client_secret_sha256',
field=models.CharField(blank=True, editable=False, max_length=255, null=True),
),
]
+4
View File
@@ -27,6 +27,9 @@ class RecordingWorkerEvent(Enum):
COMPLETED = "completed"
# The recording ended on its configured limit, its media file is available.
LIMIT_REACHED = "limit reached"
# The recording ended early without being stopped, its partial media file
# is available.
INTERRUPTED = "interrupted"
# The worker stopped before it ever started recording, there is no media file.
ABORTED = "aborted"
# The worker hit a runtime error once recording had started; its media file
@@ -44,6 +47,7 @@ SUCCESSFUL_EVENTS = frozenset(
{
RecordingWorkerEvent.COMPLETED,
RecordingWorkerEvent.LIMIT_REACHED,
RecordingWorkerEvent.INTERRUPTED,
}
)
@@ -33,6 +33,7 @@ NOTIFICATION_PREFIXES = {
}
NOTIFICATION_SUFFIXES = {
RecordingWorkerEvent.LIMIT_REACHED: "LimitReached",
RecordingWorkerEvent.INTERRUPTED: "Interrupted",
RecordingWorkerEvent.FAILED: "Failed",
RecordingWorkerEvent.ABORTED: "Aborted",
}
@@ -61,24 +62,29 @@ class RecordingEventsService:
"""
@staticmethod
def log_worker_error(recording, event, error=None, error_code=None):
"""Log FAILED at error level and expected ABORTED outcomes at info level."""
def log_worker_error(recording, event, error=None, error_code=None, details=None):
"""Log FAILED at error level, INTERRUPTED at warning level and expected
ABORTED outcomes at info level."""
if event == RecordingWorkerEvent.FAILED:
log = logger.error
elif event == RecordingWorkerEvent.INTERRUPTED:
log = logger.warning
elif event == RecordingWorkerEvent.ABORTED:
log = logger.info
else:
return
log(
"Recording worker reported %s for recording %s (room=%s, mode=%s): %s (error_code=%s)",
"Recording worker reported %s for recording %s (room=%s, mode=%s): %s "
"(error_code=%s, details=%s)",
event.value,
recording.id,
recording.room.id,
recording.mode,
error or "no error reported",
error_code or "no error_code reported",
details or "no details reported",
)
@staticmethod
@@ -196,6 +202,13 @@ class RecordingEventsService:
):
self._apply_outcome(recording, event, self._handle_limit_reached)
# Interrupted
if (
event == RecordingWorkerEvent.INTERRUPTED
and recording.status == models.RecordingStatusChoices.ACTIVE
):
self._apply_outcome(recording, event, self._handle_interrupted)
try:
self._handle_successful(recording)
except RecordingNotSavableError:
@@ -224,6 +237,20 @@ class RecordingEventsService:
cls._notify_participants(recording, RecordingWorkerEvent.LIMIT_REACHED)
@classmethod
def _handle_interrupted(cls, recording: Recording):
"""Stop recording and notify participants that only part of it is saved.
INTERRUPTED: used when the worker ended the recording early on its own
because it lost the room or ran out of resources but kept what it had
recorded so far
"""
recording.status = models.RecordingStatusChoices.STOPPED
recording.save()
cls._notify_participants(recording, RecordingWorkerEvent.INTERRUPTED)
@classmethod
def _handle_failed(cls, recording: Recording):
"""Set recording status to failed, matching the worker event, and notify participants.
+33 -1
View File
@@ -96,6 +96,9 @@ EGRESS_STATUS_TO_RECORDING_EVENT = {
api.EgressStatus.EGRESS_FAILED: RecordingWorkerEvent.FAILED,
}
EGRESS_ROOM_CONNECTION_FAILED_ERROR = "recording ended early: connection to room failed"
EGRESS_CPU_EXHAUSTED_DETAILS = "End reason: CPU exhausted"
def to_recording_event(egress_status):
"""Translate a LiveKit egress status into a recording worker event."""
@@ -109,6 +112,34 @@ def to_recording_event(egress_status):
return event
def to_ended_recording_event(egress_info):
"""Translate an ended LiveKit egress into a recording worker event.
This handles partial media file upload where an egress was interrupted
which can happen when:
- an transcription egress that lost its room connection uploads an audio file, then ends
FAILED with the EGRESS_ROOM_CONNECTION_FAILED_ERROR
- an egress drained because its host ran out of CPU which can transition to COMPLETE state
with EGRESS_CPU_EXHAUSTED_DETAILS or FAILED state if the drain outlasted its grace period
and the egress got killed, possibly before the upload.
"""
status = egress_info.status
is_cut_short = (
egress_info.error == EGRESS_ROOM_CONNECTION_FAILED_ERROR
or egress_info.details == EGRESS_CPU_EXHAUSTED_DETAILS
)
has_uploaded_file = any(file.location for file in egress_info.file_results)
if is_cut_short and (
status == api.EgressStatus.EGRESS_COMPLETE
or (status == api.EgressStatus.EGRESS_FAILED and has_uploaded_file)
):
return RecordingWorkerEvent.INTERRUPTED
return to_recording_event(status)
class LiveKitEventsService:
"""Service for processing and handling LiveKit webhook events and notifications."""
@@ -221,7 +252,7 @@ class LiveKitEventsService:
f"Recording with worker ID {data.egress_info.egress_id} does not exist"
) from err
event = to_recording_event(data.egress_info.status)
event = to_ended_recording_event(data.egress_info)
self._audit_recording_end(recording, event, data.egress_info.error_code)
# Log if/why the recording failed
@@ -230,6 +261,7 @@ class LiveKitEventsService:
event,
error=data.egress_info.error,
error_code=data.egress_info.error_code,
details=data.egress_info.details,
)
# Update room
@@ -50,6 +50,26 @@ def test_handle_limit_reached_success(mock_notify, mode, notification_type, serv
)
@pytest.mark.parametrize(
("mode", "notification_type"),
(
("screen_recording", "screenRecordingInterrupted"),
("transcript", "transcriptionInterrupted"),
),
)
@mock.patch("core.utils.notify_participants")
def test_handle_interrupted_success(mock_notify, mode, notification_type, service):
"""Test _handle_interrupted stops recording and notifies participants."""
recording = RecordingFactory(status="active", mode=mode)
service._handle_interrupted(recording)
assert recording.status == "stopped"
mock_notify.assert_called_once_with(
room_name=str(recording.room.id), notification_data={"type": notification_type}
)
@pytest.mark.parametrize(
("mode", "notification_type"),
(
@@ -222,6 +242,7 @@ def test_handle_update_syncs_room_metadata(
RecordingWorkerEvent.STARTING,
RecordingWorkerEvent.COMPLETED,
RecordingWorkerEvent.LIMIT_REACHED,
RecordingWorkerEvent.INTERRUPTED,
RecordingWorkerEvent.ABORTED,
RecordingWorkerEvent.FAILED,
),
@@ -244,6 +265,10 @@ def test_handle_update_ignores_events_without_a_metadata_status(
(RecordingWorkerEvent.LIMIT_REACHED, "active", "saved", "LimitReached"),
(RecordingWorkerEvent.LIMIT_REACHED, "stopped", "saved", None),
(RecordingWorkerEvent.LIMIT_REACHED, "saved", "saved", None),
(RecordingWorkerEvent.INTERRUPTED, "active", "saved", "Interrupted"),
(RecordingWorkerEvent.INTERRUPTED, "stopped", "saved", None),
(RecordingWorkerEvent.INTERRUPTED, "saved", "saved", None),
(RecordingWorkerEvent.INTERRUPTED, "failed_to_stop", "failed_to_stop", None),
(RecordingWorkerEvent.ABORTED, "active", "aborted", "Aborted"),
(RecordingWorkerEvent.ABORTED, "failed_to_stop", "failed_to_stop", None),
(RecordingWorkerEvent.FAILED, "active", "failed", "Failed"),
@@ -321,6 +346,7 @@ def test_handle_terminal_event_ignores_non_terminal_events(
("event", "expected_status"),
(
(RecordingWorkerEvent.LIMIT_REACHED, "saved"),
(RecordingWorkerEvent.INTERRUPTED, "saved"),
(RecordingWorkerEvent.ABORTED, "aborted"),
(RecordingWorkerEvent.FAILED, "failed"),
),
@@ -407,6 +433,7 @@ def test_handle_update_survives_a_metadata_failure(
("event", "expected_level"),
(
(RecordingWorkerEvent.ABORTED, logging.INFO),
(RecordingWorkerEvent.INTERRUPTED, logging.WARNING),
(RecordingWorkerEvent.FAILED, logging.ERROR),
),
)
@@ -419,7 +446,11 @@ def test_log_worker_error_reports_an_unsuccessful_event(
with caplog.at_level(logging.INFO):
service.log_worker_error(
recording, event, error="could not connect to the room", error_code=500
recording,
event,
error="could not connect to the room",
error_code=500,
details="End reason: Failure",
)
assert (
@@ -428,6 +459,7 @@ def test_log_worker_error_reports_an_unsuccessful_event(
)
assert "could not connect to the room" in caplog.text
assert "error_code=500" in caplog.text
assert "details=End reason: Failure" in caplog.text
worker_logs = [
record
for record in caplog.records
@@ -448,7 +480,7 @@ def test_log_worker_error_reports_an_unsuccessful_event(
),
)
def test_log_worker_error_stays_quiet_on_anything_else(event, service, caplog):
"""Test log_worker_error ignores events other than FAILED and ABORTED."""
"""Test log_worker_error ignores events other than FAILED, INTERRUPTED and ABORTED."""
recording = RecordingFactory(status="active")
@@ -66,6 +66,8 @@ def test_api_rooms_create_authenticated(reset_cache):
assert room.name == "my room"
assert room.slug == "my-room"
assert room.accesses.filter(role="owner", user=user).exists() is True
assert "livekit" not in response.json()
assert response.json()["pin_code"] == room.pin_code
rooms_data = cache.keys("room-creation-callback_*")
assert not rooms_data
@@ -495,3 +497,4 @@ def test_api_rooms_create_authenticated_with_user_access_token():
assert response.status_code == 201
room = Room.objects.get()
assert room.accesses.filter(role="owner", user=user).exists()
assert "livekit" not in response.json()
@@ -0,0 +1,580 @@
"""
Test rooms API endpoints in the Meet core app: join.
"""
# pylint: disable=redefined-outer-name
from unittest import mock
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.utils.crypto import get_random_string
import jwt
import pytest
from faker import Faker
from rest_framework.test import APIClient
from ...factories import (
RoomFactory,
UserFactory,
UserResourceAccessFactory,
)
from ...models import RoleChoices, RoomAccessLevel
from ..utils import generate_user_access_token
fake = Faker()
pytestmark = pytest.mark.django_db
livekit_token = fake.sha256()
ANONYMOUS = "anonymous"
NO_ROLE = "no-role"
# Who may join a room directly, without going through the lobby
# room.get_role(user) returns None both for an anonymous user and for an
# authenticated user without access, hence the NO_ROLE
ACCESS_MATRIX = [
(RoomAccessLevel.PUBLIC, ANONYMOUS, True),
(RoomAccessLevel.TRUSTED, ANONYMOUS, False),
(RoomAccessLevel.RESTRICTED, ANONYMOUS, False),
(RoomAccessLevel.PUBLIC, NO_ROLE, True),
(RoomAccessLevel.TRUSTED, NO_ROLE, True),
(RoomAccessLevel.RESTRICTED, NO_ROLE, False),
*[
(access_level, role, True)
for access_level in RoomAccessLevel
for role in RoleChoices
],
]
def decode_token(token):
"""Decode a LiveKit access token for inspection."""
return jwt.decode(
token,
settings.LIVEKIT_CONFIGURATION["api_secret"],
algorithms=["HS256"],
)
def join_url(room_id):
"""Return the url to join a room."""
return f"/api/v1.0/rooms/{room_id!s}/join/"
@pytest.mark.parametrize(
("access_level", "requester", "can_join_directly"), ACCESS_MATRIX
)
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_access(mock_token, access_level, requester, can_join_directly):
"""
Joining a room should return LiveKit credentials and the pin code only to
users who may enter without going through the lobby, and the accesses
only to the room's administrators and owners.
"""
room = RoomFactory(
access_level=access_level,
configuration={"can_publish_sources": ["camera"]},
)
client = APIClient()
user = AnonymousUser()
username = fake.user_name()
role = None
if requester != ANONYMOUS:
user = UserFactory()
client.force_login(user)
if requester != NO_ROLE:
role = requester
UserResourceAccessFactory(resource=room, user=user, role=role)
response = client.post(join_url(room.id), {"username": username}, format="json")
assert response.status_code == 200
content = response.json()
accesses = content.pop("accesses", None)
if role in (RoleChoices.ADMIN, RoleChoices.OWNER):
assert {access["id"] for access in accesses} == {
str(access.id) for access in room.accesses.all()
}
else:
assert accesses is None
expected = {
"id": str(room.id),
"name": room.name,
"slug": room.slug,
"configuration": {"can_publish_sources": ["camera"]},
"access_level": str(access_level),
}
if can_join_directly:
expected["pin_code"] = room.pin_code
expected["livekit"] = {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": str(room.id),
"token": livekit_token,
}
assert content == expected
if can_join_directly:
mock_token.assert_called_once_with(
room=str(room.id),
user=user,
username=username,
color=None,
sources=["camera"],
role=role,
participant_id=None,
)
else:
mock_token.assert_not_called()
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_by_slug(mock_token):
"""It should be possible to join a room by its slug, not normalized."""
room = RoomFactory(name="Réunion", access_level=RoomAccessLevel.PUBLIC)
response = APIClient().post(join_url("Réunion"), {}, format="json")
assert response.status_code == 200
assert response.json()["id"] == str(room.id)
assert response.json()["livekit"]["room"] == str(room.id)
mock_token.assert_called_once()
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_members_num_queries(mock_token, django_assert_num_queries):
"""The role of the requester should only be looked up once."""
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
UserResourceAccessFactory(resource=room, user=user, role=RoleChoices.MEMBER)
client = APIClient()
client.force_login(user)
with django_assert_num_queries(3):
response = client.post(join_url(room.id), {}, format="json")
assert response.status_code == 200
assert "livekit" in response.json()
assert mock_token.call_args.kwargs["role"] == RoleChoices.MEMBER
@pytest.mark.parametrize("role", [RoleChoices.ADMIN, RoleChoices.OWNER])
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_administrators_num_queries(
mock_token, role, django_assert_num_queries
):
"""Only the accesses should be queried on top of the role for administrators."""
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
UserResourceAccessFactory(resource=room, user=user, role=role)
client = APIClient()
client.force_login(user)
with django_assert_num_queries(4):
response = client.post(join_url(room.id), {}, format="json")
assert response.status_code == 200
assert "livekit" in response.json()
assert "accesses" in response.json()
assert mock_token.call_args.kwargs["role"] == role
def test_api_rooms_join_get_not_allowed():
"""Joining a room should only be possible with a POST request."""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
response = APIClient().get(join_url(room.id))
assert response.status_code == 405
@pytest.mark.parametrize("authenticated", [False, True])
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_unregistered_allowed(mock_token, authenticated, settings):
"""
Joining an unregistered room should return LiveKit credentials for the
room named after the slug, when unregistered rooms are allowed.
"""
settings.ALLOW_UNREGISTERED_ROOMS = True
client = APIClient()
user = AnonymousUser()
username = fake.user_name()
if authenticated:
user = UserFactory()
client.force_login(user)
response = client.post(
join_url("unregistered-room"), {"username": username}, format="json"
)
assert response.status_code == 200
assert response.json() == {
"id": None,
"slug": "unregistered-room",
"access_level": "public",
"livekit": {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": "unregistered-room",
"token": livekit_token,
},
}
mock_token.assert_called_once_with(
room="unregistered-room",
user=user,
username=username,
color=None,
sources=None,
role=None,
participant_id=None,
)
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_unregistered_allowed_not_normalized(mock_token, settings):
"""
Joining an unregistered room by a slug that is not normalized should use
the LiveKit room named after the url-safe slug.
"""
settings.ALLOW_UNREGISTERED_ROOMS = True
response = APIClient().post(join_url("Réunion"), {}, format="json")
assert response.status_code == 200
assert response.json() == {
"id": None,
"slug": "reunion",
"access_level": "public",
"livekit": {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": "reunion",
"token": livekit_token,
},
}
mock_token.assert_called_once_with(
room="reunion",
user=AnonymousUser(),
username=None,
color=None,
sources=None,
role=None,
participant_id=None,
)
def test_api_rooms_join_unregistered_token_claims(settings):
"""The token of an unregistered room should be scoped to it and carry the name."""
settings.ALLOW_UNREGISTERED_ROOMS = True
username = fake.user_name()
response = APIClient().post(
join_url("unregistered-room"), {"username": username}, format="json"
)
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == username
assert claims["video"]["room"] == "unregistered-room"
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_unregistered_not_allowed(mock_token, settings):
"""
Joining an unregistered room should return a 404 if unregistered rooms
are not allowed.
"""
settings.ALLOW_UNREGISTERED_ROOMS = False
response = APIClient().post(join_url("unregistered-room"), {}, format="json")
assert response.status_code == 404
assert response.json() == {"detail": "No Room matches the given query."}
mock_token.assert_not_called()
@pytest.mark.parametrize("lookup", ["---", "!!!", "_"])
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_unregistered_empty_slug(mock_token, lookup, settings):
"""
Joining an unregistered room whose slug is empty once normalized should
return a 404 rather than credentials for a room with no name.
"""
settings.ALLOW_UNREGISTERED_ROOMS = True
response = APIClient().post(join_url(lookup), {}, format="json")
assert response.status_code == 404
assert response.json() == {"detail": "No Room matches the given query."}
mock_token.assert_not_called()
@pytest.mark.parametrize("template", ["{}!", "_{}", "{}~", "{}!!"])
@pytest.mark.parametrize(
"access_level", [RoomAccessLevel.TRUSTED, RoomAccessLevel.RESTRICTED]
)
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_unregistered_uuid_slug(
mock_token, access_level, template, settings
):
"""
Joining an unregistered room whose slug is a UUID once normalized should
return a 404 rather than credentials for the registered room with that id.
"""
settings.ALLOW_UNREGISTERED_ROOMS = True
room = RoomFactory(access_level=access_level)
response = APIClient().post(
join_url(template.format(str(room.id).upper())), {}, format="json"
)
assert response.status_code == 404
assert response.json() == {"detail": "No Room matches the given query."}
mock_token.assert_not_called()
@pytest.mark.parametrize("allow_unregistered_rooms", [True, False])
@pytest.mark.parametrize("lookup", ["id", "slug"])
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_soft_deleted(
mock_token, lookup, allow_unregistered_rooms, settings
):
"""
Joining a soft-deleted room should return a 410, and never fall back
to an unregistered room with the same slug.
"""
settings.ALLOW_UNREGISTERED_ROOMS = allow_unregistered_rooms
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
room.soft_delete()
response = APIClient().post(join_url(getattr(room, lookup)), {}, format="json")
assert response.status_code == 410
assert response.json() == {"detail": "This room has been deleted."}
mock_token.assert_not_called()
@pytest.mark.parametrize(
("payload", "expected_username"),
[
({"username": "Jean Bon"}, "Jean Bon"),
({"username": " Jean Bon "}, "Jean Bon"),
({"username": "x" * 255}, "x" * 255),
({}, None),
({"username": None}, None),
({"username": ""}, None),
({"username": " "}, None),
],
)
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_username(mock_token, payload, expected_username):
"""
The trimmed display name should be passed on to the token, a missing or
blank one leaving the default name to the token generation.
"""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
response = APIClient().post(join_url(room.id), payload, format="json")
assert response.status_code == 200
assert response.json()["livekit"]["token"] == livekit_token
assert mock_token.call_args.kwargs["username"] == expected_username
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_username_too_long(mock_token):
"""A display name exceeding 255 characters should be rejected."""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
response = APIClient().post(
join_url(room.id), {"username": "a" * 256}, format="json"
)
assert response.status_code == 400
assert response.json() == {
"username": ["Ensure this field has no more than 255 characters."]
}
mock_token.assert_not_called()
@mock.patch("core.utils.generate_token", return_value=livekit_token)
def test_api_rooms_join_username_in_query_string_ignored(mock_token):
"""The display name should only be read from the request body."""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
username = fake.user_name()
response = APIClient().post(
f"{join_url(room.id)}?username={username}", {}, format="json"
)
assert response.status_code == 200
assert mock_token.call_args.kwargs["username"] is None
def test_api_rooms_join_token_claims_anonymous():
"""The token should carry the submitted display name and be scoped to the room."""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
username = fake.user_name()
response = APIClient().post(
join_url(room.id), {"username": username}, format="json"
)
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == username
assert claims["video"]["room"] == str(room.id)
assert claims["video"]["roomAdmin"] is False
def test_api_rooms_join_token_claims_anonymous_default_name():
"""An anonymous user joining without a display name should get the default one."""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
response = APIClient().post(join_url(room.id), {"username": ""}, format="json")
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == "Anonymous"
def test_api_rooms_join_token_claims_owner():
"""The token of an owner should carry the submitted name and the room admin grant."""
username = fake.user_name()
user = UserFactory(full_name=fake.name())
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED, users=[(user, "owner")])
client = APIClient()
client.force_login(user)
response = client.post(join_url(room.id), {"username": username}, format="json")
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == username
assert claims["sub"] == str(user.sub)
assert claims["video"]["roomAdmin"] is True
def test_api_rooms_join_display_name_edit_disabled(settings):
"""
An authenticated user should keep their account's name when display name
edition is disabled, whatever name they submit.
"""
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
full_name = f"{fake.first_name()} {fake.last_name()}"
user = UserFactory(full_name=full_name)
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient()
client.force_login(user)
response = client.post(join_url(room.id), {"username": "Impostor"}, format="json")
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == full_name
def test_api_rooms_join_display_name_edit_disabled_anonymous(settings):
"""Anonymous users should still choose their display name when edition is disabled."""
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
username = fake.user_name()
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
response = APIClient().post(
join_url(room.id), {"username": username}, format="json"
)
assert response.status_code == 200
claims = decode_token(response.json()["livekit"]["token"])
assert claims["name"] == username
def test_api_rooms_join_session_without_csrf_token():
"""A session-authenticated request without a CSRF token should be rejected."""
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient(enforce_csrf_checks=True)
client.force_login(user)
response = client.post(join_url(room.id), {"username": "Jane"}, format="json")
assert response.status_code == 403
assert response.json()["detail"].startswith("CSRF Failed")
def test_api_rooms_join_session_with_csrf_token():
"""A session-authenticated request with a valid CSRF token should be accepted."""
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient(enforce_csrf_checks=True)
client.force_login(user)
csrf_secret = get_random_string(32)
client.cookies[settings.CSRF_COOKIE_NAME] = csrf_secret
response = client.post(
join_url(room.id),
{"username": "Jane"},
format="json",
HTTP_X_CSRFTOKEN=csrf_secret,
)
assert response.status_code == 200
assert "livekit" in response.json()
def test_api_rooms_join_session_with_csrf_token_foreign_origin():
"""A session-authenticated request from a foreign origin should be rejected."""
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient(enforce_csrf_checks=True)
client.force_login(user)
csrf_secret = get_random_string(32)
client.cookies[settings.CSRF_COOKIE_NAME] = csrf_secret
response = client.post(
join_url(room.id),
{"username": "Jane"},
format="json",
HTTP_X_CSRFTOKEN=csrf_secret,
HTTP_ORIGIN="https://evil.example",
)
assert response.status_code == 403
assert response.json()["detail"].startswith("CSRF Failed: Origin checking failed")
def test_api_rooms_join_anonymous_without_csrf_token():
"""
An anonymous request carries no ambient credential, so it should not be
subject to CSRF checks.
"""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient(enforce_csrf_checks=True)
response = client.post(join_url(room.id), {"username": "Anonymous"}, format="json")
assert response.status_code == 200
assert "livekit" in response.json()
def test_api_rooms_join_user_access_token_without_csrf_token():
"""
A request authenticated with a user access token carries no ambient
credential, so it should not be subject to CSRF checks.
"""
username = fake.user_name()
user = UserFactory()
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED, users=[(user, "owner")])
client = APIClient(enforce_csrf_checks=True)
client.credentials(HTTP_AUTHORIZATION=f"Bearer {generate_user_access_token(user)}")
response = client.post(join_url(room.id), {"username": username}, format="json")
assert response.status_code == 200
assert response.json()["pin_code"] == room.pin_code
assert "accesses" in response.json()
claims = decode_token(response.json()["livekit"]["token"])
assert claims["sub"] == str(user.sub)
@@ -61,6 +61,7 @@ def test_api_rooms_list_authenticated():
}
results_id = {result["id"] for result in results}
assert expected_ids == results_id
assert all("livekit" not in result for result in results)
@mock.patch.object(PageNumberPagination, "get_page_size", return_value=2)
@@ -172,3 +173,4 @@ def test_api_rooms_list_authenticated_with_user_access_token():
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["id"] == str(room.id)
assert "livekit" not in response.data["results"][0]
@@ -2,7 +2,7 @@
Test rooms API endpoints in the Meet core app: lobby functionality.
"""
# pylint: disable=W0621,W0613,W0212
# pylint: disable=W0621,W0613,W0212,too-many-lines
import uuid
from unittest import mock
@@ -360,6 +360,40 @@ def test_request_entry_invalid_data():
assert response.status_code == 400
def test_request_entry_username_too_long():
"""Should return 400 for a username exceeding 255 characters."""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
client = APIClient()
with mock.patch.object(utils, "notify_participants") as mock_notify:
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "a" * 256},
)
assert response.status_code == 400
assert response.json() == {
"username": ["Ensure this field has no more than 255 characters."]
}
mock_notify.assert_not_called()
assert not LobbyService()._index_members(room.id)
def test_request_entry_username_at_max_length():
"""Should accept a username of exactly 255 characters."""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
client = APIClient()
with mock.patch.object(utils, "notify_participants", return_value=None):
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "a" * 255},
)
assert response.status_code == 200
assert response.json()["username"] == "a" * 255
def test_request_entry_room_not_found():
"""Should return 404 for non-existent room."""
client = APIClient()
@@ -166,6 +166,30 @@ def test_mute_participant_everyone_can_mute_disabled_blocks_non_admin(
mock_livekit_client.room.mute_published_track.assert_not_called()
def test_mute_participant_everyone_can_mute_null_allows_non_admin(
mock_livekit_client,
):
"""Should treat a stored null everyone_can_mute as unset, hence enabled."""
client = APIClient()
room = RoomFactory(configuration={"everyone_can_mute": None})
user = AnonymousUser()
token = utils.generate_token(str(room.id), user)
url = reverse("rooms-mute-participant", kwargs={"pk": room.id})
response = client.post(
url,
{"participant_identity": str(uuid4()), "track_sid": "test-track-sid"},
format="json",
HTTP_AUTHORIZATION=f"X-LiveKit-Token {token}",
)
assert response.status_code == status.HTTP_200_OK
assert response.data == {"status": "success"}
mock_livekit_client.room.mute_published_track.assert_called_once()
def test_mute_participant_everyone_can_mute_disabled_allows_admin(mock_livekit_client):
"""Should allow admins and owners to mute when everyone_can_mute is False."""
client = APIClient()
@@ -782,7 +806,7 @@ def test_update_participant_invalid_permission():
"loc": ["invalid-attributes"],
"msg": "Extra inputs are not permitted",
"input": "True",
"url": "https://errors.pydantic.dev/2.13/v/extra_forbidden",
"url": "https://errors.pydantic.dev/2.14/v/extra_forbidden",
},
]
}
@@ -366,6 +366,22 @@ def test_rename_participant_name_too_long(room, token):
assert "name" in response.data
def test_rename_participant_name_at_max_length(mock_livekit_client, room, token):
"""Test rename accepts a name of exactly 255 characters."""
client = APIClient()
url = reverse("rooms-rename", kwargs={"pk": room.id})
response = client.post(
url,
{"name": "a" * 255},
format="json",
HTTP_AUTHORIZATION=f"X-LiveKit-Token {token}",
)
assert response.status_code == status.HTTP_200_OK
call_kwargs = mock_livekit_client.room.update_participant.call_args
assert call_kwargs[0][0].name == "a" * 255
def test_rename_participant_forbidden_without_token(room):
"""Test rename returns 403 when no LiveKit token is provided."""
client = APIClient()
@@ -1,11 +1,8 @@
"""
Test rooms API endpoints in the Meet core app: retrieve.
"""
"""Test rooms API endpoints in the Meet core app: retrieve."""
import random
from unittest import mock
from django.contrib.auth.models import AnonymousUser
from django.test.utils import override_settings
from django.utils import timezone as dj_timezone
@@ -112,18 +109,11 @@ def test_api_rooms_retrieve_anonymous_private_slug_not_normalized():
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
@mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
"""
Retrieving an unregistered room should return a Livekit token
if unregistered rooms are allowed.
Retrieving an unregistered room should describe it if unregistered rooms
are allowed, without any LiveKit token: those are obtained by joining.
"""
client = APIClient()
response = client.get("/api/v1.0/rooms/unregistered-room/")
@@ -133,32 +123,17 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
"id": None,
"slug": "unregistered-room",
"access_level": "public",
"is_administrable": False,
"livekit": {
"url": "test_url_value",
"room": "unregistered-room",
"token": "foo",
},
}
mock_token.assert_called_once_with(
room="unregistered-room", user=AnonymousUser(), username=None
)
mock_token.assert_not_called()
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
@mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_token):
"""
Getting an unregistered room by a slug that is not normalized should work
and use the Livekit room on the url-safe name.
and return the url-safe slug.
"""
client = APIClient()
response = client.get("/api/v1.0/rooms/Réunion/")
@@ -168,17 +143,9 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_t
"id": None,
"slug": "reunion",
"access_level": "public",
"is_administrable": False,
"livekit": {
"url": "test_url_value",
"room": "reunion",
"token": "foo",
},
}
mock_token.assert_called_once_with(
room="reunion", user=AnonymousUser(), username=None
)
mock_token.assert_not_called()
@override_settings(ALLOW_UNREGISTERED_ROOMS=False)
@@ -193,6 +160,36 @@ def test_api_rooms_retrieve_anonymous_unregistered_not_allowed():
assert response.json() == {"detail": "No Room matches the given query."}
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@pytest.mark.parametrize("lookup", ["---", "!!!", "_"])
def test_api_rooms_retrieve_anonymous_unregistered_empty_slug(lookup):
"""
Retrieving an unregistered room whose slug is empty once normalized should
return a 404.
"""
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{lookup}/")
assert response.status_code == 404
assert response.json() == {"detail": "No Room matches the given query."}
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@pytest.mark.parametrize("template", ["{}!", "_{}", "{}~"])
def test_api_rooms_retrieve_anonymous_unregistered_uuid_slug(template):
"""
Retrieving an unregistered room whose slug is a UUID once normalized should
return a 404.
"""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{template.format(room.id)}/")
assert response.status_code == 404
assert response.json() == {"detail": "No Room matches the given query."}
@pytest.mark.parametrize("allow_unregistered_rooms", [True, False])
@pytest.mark.parametrize("lookup", ["id", "slug"])
@mock.patch("core.utils.generate_token", return_value="foo")
@@ -216,52 +213,33 @@ def test_api_rooms_retrieve_soft_deleted(
@mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_anonymous_public(mock_token):
"""
Anonymous users should be able to retrieve a room with a token provided, if the room is public.
Anonymous users should be able to retrieve a public room with its pin code,
but should not be given any token: those are obtained by joining.
"""
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
expected_name = f"{room.id!s}"
assert response.json() == {
"configuration": {},
"access_level": str(room.access_level),
"id": str(room.id),
"livekit": {
"url": "test_url_value",
"room": expected_name,
"token": "foo",
},
"name": room.name,
"pin_code": room.pin_code,
"slug": room.slug,
}
mock_token.assert_called_once()
mock_token.assert_not_called()
@mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_authenticated_public(mock_token):
"""
Authenticated users should be allowed to retrieve a room and get a token for a room to
which they are not related, provided the room is public.
Authenticated users should be allowed to retrieve a public room to which
they are not related, with its pin code but without any token.
They should not see related users.
"""
room = RoomFactory(
@@ -278,44 +256,23 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
)
assert response.status_code == 200
expected_name = f"{room.id!s}"
assert response.json() == {
"configuration": {"can_publish_sources": ["camera"]},
"access_level": str(room.access_level),
"id": str(room.id),
"livekit": {
"url": "test_url_value",
"room": expected_name,
"token": "foo",
},
"name": room.name,
"pin_code": room.pin_code,
"slug": room.slug,
}
mock_token.assert_called_once_with(
room=expected_name,
user=user,
username=None,
color=None,
sources=["camera"],
role=None,
participant_id=None,
)
mock_token.assert_not_called()
@mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_authenticated_trusted(mock_token):
"""
Authenticated users should be allowed to retrieve a room and get a token for a room to
which they are not related, provided the room has a trusted access_level.
Authenticated users should be allowed to retrieve a trusted room to which
they are not related, with its pin code but without any token.
They should not see related users.
"""
room = RoomFactory(access_level=RoomAccessLevel.TRUSTED)
@@ -329,30 +286,16 @@ def test_api_rooms_retrieve_authenticated_trusted(mock_token):
)
assert response.status_code == 200
expected_name = f"{room.id!s}"
assert response.json() == {
"configuration": {},
"access_level": str(room.access_level),
"id": str(room.id),
"livekit": {
"url": "test_url_value",
"room": expected_name,
"token": "foo",
},
"name": room.name,
"pin_code": room.pin_code,
"slug": room.slug,
}
mock_token.assert_called_once_with(
room=expected_name,
user=user,
username=None,
color=None,
sources=None,
role=None,
participant_id=None,
)
mock_token.assert_not_called()
def test_api_rooms_retrieve_authenticated():
@@ -381,13 +324,6 @@ def test_api_rooms_retrieve_authenticated():
@mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, settings):
"""
Users who are members of a room should not be allowed to see related users.
@@ -415,40 +351,19 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
assert "accesses" not in content_dict
expected_name = str(room.id)
assert content_dict == {
"configuration": {"can_publish_sources": ["camera"]},
"access_level": str(room.access_level),
"id": str(room.id),
"livekit": {
"url": "test_url_value",
"room": expected_name,
"token": "foo",
},
"name": room.name,
"pin_code": room.pin_code,
"slug": room.slug,
}
mock_token.assert_called_once_with(
room=expected_name,
user=user,
username=None,
color=None,
sources=["camera"],
role=str(RoleChoices.MEMBER),
participant_id=None,
)
mock_token.assert_not_called()
@mock.patch("core.utils.generate_token", return_value="foo")
@override_settings(
LIVEKIT_CONFIGURATION={
"api_key": "key",
"api_secret": "secret",
"url": "test_url_value",
}
)
def test_api_rooms_retrieve_administrators(
mock_token, django_assert_num_queries, settings
):
@@ -511,30 +426,16 @@ def test_api_rooms_retrieve_administrators(
],
key=lambda x: x["id"],
)
expected_name = str(room.id)
assert content_dict == {
"access_level": str(room.access_level),
"id": str(room.id),
"configuration": {},
"livekit": {
"url": "test_url_value",
"room": expected_name,
"token": "foo",
},
"name": room.name,
"pin_code": room.pin_code,
"slug": room.slug,
}
mock_token.assert_called_once_with(
room=expected_name,
user=user,
username=None,
color=None,
sources=None,
role=str(user_access.role),
participant_id=None,
)
mock_token.assert_not_called()
@pytest.mark.parametrize("access_level", RoomAccessLevel)
@@ -567,3 +468,77 @@ def test_api_rooms_retrieve_authenticated_with_user_access_token():
assert response.data["id"] == str(room.id)
assert response.data["pin_code"] == room.pin_code
assert "accesses" in response.data
assert "livekit" not in response.data
@pytest.mark.parametrize("access_level", RoomAccessLevel)
@pytest.mark.parametrize("role", [None, *RoleChoices])
@mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_never_returns_livekit_credentials(
mock_token, role, access_level
):
"""Retrieving a room should never mint a LiveKit token."""
room = RoomFactory(access_level=access_level)
user = UserFactory()
if role is not None:
UserResourceAccessFactory(resource=room, user=user, role=role)
client = APIClient()
client.force_login(user)
response = client.get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
assert "livekit" not in response.json()
mock_token.assert_not_called()
@pytest.mark.parametrize("access_level", RoomAccessLevel)
@mock.patch("core.utils.generate_token", return_value="foo")
def test_api_rooms_retrieve_anonymous_never_returns_livekit_credentials(
mock_token, access_level
):
"""An anonymous user should never get a LiveKit token by retrieving a room."""
room = RoomFactory(access_level=access_level)
response = APIClient().get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
assert "livekit" not in response.json()
mock_token.assert_not_called()
@pytest.mark.parametrize(
("access_level", "authenticated", "role", "pin_code_exposed"),
[
(RoomAccessLevel.PUBLIC, False, None, True),
(RoomAccessLevel.TRUSTED, False, None, False),
(RoomAccessLevel.RESTRICTED, False, None, False),
(RoomAccessLevel.PUBLIC, True, None, True),
(RoomAccessLevel.TRUSTED, True, None, True),
(RoomAccessLevel.RESTRICTED, True, None, False),
(RoomAccessLevel.RESTRICTED, True, RoleChoices.MEMBER, True),
(RoomAccessLevel.RESTRICTED, True, RoleChoices.ADMIN, True),
(RoomAccessLevel.RESTRICTED, True, RoleChoices.OWNER, True),
],
)
def test_api_rooms_retrieve_pin_code_exposure(
access_level, authenticated, role, pin_code_exposed
):
"""
The pin code should only be exposed to users who may join the room
without going through the lobby.
"""
room = RoomFactory(access_level=access_level)
client = APIClient()
if authenticated:
user = UserFactory()
if role is not None:
UserResourceAccessFactory(resource=room, user=user, role=role)
client.force_login(user)
response = client.get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
assert ("pin_code" in response.json()) is pin_code_exposed
if pin_code_exposed:
assert response.json()["pin_code"] == room.pin_code
@@ -113,6 +113,7 @@ def test_api_rooms_update_administrators(mock_update_metadata):
format="json",
)
assert response.status_code == 200
assert "livekit" not in response.json()
room.refresh_from_db()
assert room.name == "New name"
assert room.slug == "new-name"
@@ -467,6 +468,7 @@ def test_api_rooms_update_authenticated_with_user_access_token(privileged_role):
room.accesses.filter(user=user).update(role=privileged_role)
response = client.patch(f"/api/v1.0/rooms/{room.id!s}/", {"name": "new name"})
assert response.status_code == 200
assert "livekit" not in response.json()
room.refresh_from_db()
assert room.name == "new name"
@@ -8,9 +8,12 @@ import hashlib
import json
from unittest import mock
from django.utils import timezone
import pytest
from livekit import api
from ...factories import RecordingFactory
from ...services.livekit_events import ActionFailedError, LiveKitEventsService
@@ -184,3 +187,75 @@ def test_action_error(client, mock_livekit_config):
content_type="application/json",
HTTP_AUTHORIZATION=auth_token,
)
@pytest.mark.django_db
@mock.patch(
"core.recording.services.recording_events.notification_service."
"notify_external_services",
return_value=False,
)
@mock.patch("core.utils.notify_participants")
@mock.patch("core.services.room_management.RoomManagement.update_metadata")
def test_egress_ended_keeps_a_partial_transcript(
mock_update_metadata,
mock_notify,
mock_notify_external_services,
client,
mock_livekit_config,
):
"""Should save a transcript whose egress lost the room but uploaded its file.
LiveKit Egress v1.14 reports it as failed. The payload mirrors the one it
sends, including a field unknown to the pinned livekit-protocol, which
must be ignored rather than rejected.
"""
recording = RecordingFactory(
worker_id="EG_hycBMAjmt6Ub", mode="transcript", status="active"
)
event_data = json.dumps(
{
"event": "egress_ended",
"egressInfo": {
"egressId": "EG_hycBMAjmt6Ub",
"roomId": "RM_hycBMAjmt6Ub",
"roomName": str(recording.room.id),
"status": "EGRESS_FAILED",
"error": "recording ended early: connection to room failed",
"errorCode": 503,
"details": "End reason: Source closed",
"fileResults": [
{
"filename": f"recordings/{recording.id}.ogg",
"location": f"https://s3.example.com/recordings/{recording.id}.ogg",
"size": "123456",
}
],
},
"id": "EV_eugWmGhovZmm",
"createdAt": str(int(timezone.now().timestamp())),
}
)
hash64 = base64.b64encode(hashlib.sha256(event_data.encode()).digest()).decode()
token = api.AccessToken(
mock_livekit_config["api_key"], mock_livekit_config["api_secret"]
)
token.claims.sha256 = hash64
response = client.post(
"/api/v1.0/rooms/webhooks-livekit/",
data=event_data,
content_type="application/json",
HTTP_AUTHORIZATION=token.to_jwt(),
)
assert response.status_code == 200
mock_notify.assert_called_once_with(
room_name=str(recording.room.id),
notification_data={"type": "transcriptionInterrupted"},
)
mock_notify_external_services.assert_called_once_with(recording)
recording.refresh_from_db()
assert recording.status == "saved"
@@ -1,7 +1,7 @@
"""
Test LiveKitEvents service.
"""
# pylint: disable=W0621,W0613, W0212, E0611, C0302
# pylint: disable=W0621,W0613, W0212, E0611, E1101, C0302
import logging
import uuid
@@ -11,6 +11,7 @@ from unittest import mock
from django.utils import timezone
import pytest
from faker import Faker
from livekit.api import EgressStatus
from core.audit.testing import find_events
@@ -19,12 +20,15 @@ from core.models import Room
from core.recording.enums import RecordingWorkerEvent
from core.recording.services.recording_events import RecordingEventsService
from core.services.livekit_events import (
EGRESS_CPU_EXHAUSTED_DETAILS,
EGRESS_ROOM_CONNECTION_FAILED_ERROR,
EGRESS_STATUS_TO_RECORDING_EVENT,
ActionFailedError,
AuthenticationError,
InvalidPayloadError,
LiveKitEventsService,
api,
to_ended_recording_event,
to_recording_event,
)
from core.services.lobby import LobbyService
@@ -39,6 +43,7 @@ from core.services.sip_management import (
from core.utils import NotificationError
pytestmark = pytest.mark.django_db
fake = Faker()
@pytest.fixture
@@ -494,6 +499,228 @@ def test_to_recording_event_returns_none_on_unmapped_status(caplog):
assert "Unmapped LiveKit egress status" in caplog.text
recording_filename = f"recordings/{fake.file_name(extension='ogg')}"
UPLOADED_FILE = api.FileInfo(
filename=recording_filename,
location=f"https://storage.example.com/{recording_filename}",
size=1048576,
)
NOT_UPLOADED_FILE = api.FileInfo(filename=recording_filename)
ROOM_CONNECTION_FAILED = {
"status": EgressStatus.EGRESS_FAILED,
"error": EGRESS_ROOM_CONNECTION_FAILED_ERROR,
"error_code": 503,
"details": "End reason: Source closed",
}
CPU_EXHAUSTED_DRAINED = {
"status": EgressStatus.EGRESS_COMPLETE,
"details": EGRESS_CPU_EXHAUSTED_DETAILS,
}
CPU_EXHAUSTED_KILLED = {
"status": EgressStatus.EGRESS_FAILED,
"error": "CPU exhausted: 8.5 cores used",
"error_code": 403,
"details": EGRESS_CPU_EXHAUSTED_DETAILS,
}
@pytest.mark.parametrize(
("egress_info", "expected_event"),
(
(
api.EgressInfo(**ROOM_CONNECTION_FAILED, file_results=[UPLOADED_FILE]),
RecordingWorkerEvent.INTERRUPTED,
),
(
api.EgressInfo(**ROOM_CONNECTION_FAILED, file_results=[NOT_UPLOADED_FILE]),
RecordingWorkerEvent.FAILED,
),
(
api.EgressInfo(**CPU_EXHAUSTED_DRAINED, file_results=[UPLOADED_FILE]),
RecordingWorkerEvent.INTERRUPTED,
),
(
api.EgressInfo(**CPU_EXHAUSTED_KILLED, file_results=[UPLOADED_FILE]),
RecordingWorkerEvent.INTERRUPTED,
),
(
api.EgressInfo(**CPU_EXHAUSTED_KILLED, file_results=[NOT_UPLOADED_FILE]),
RecordingWorkerEvent.FAILED,
),
# Other failures stay failures, even with an uploaded file.
(
api.EgressInfo(
status=EgressStatus.EGRESS_FAILED,
error="pipeline frozen",
error_code=500,
details="End reason: Source closed",
file_results=[UPLOADED_FILE],
),
RecordingWorkerEvent.FAILED,
),
(
api.EgressInfo(
status=EgressStatus.EGRESS_COMPLETE,
details="End reason: StopEgress API",
file_results=[UPLOADED_FILE],
),
RecordingWorkerEvent.COMPLETED,
),
(
api.EgressInfo(
status=EgressStatus.EGRESS_ABORTED,
error="Stop called before pipeline could start",
error_code=412,
details="End reason: StopEgress API",
),
RecordingWorkerEvent.ABORTED,
),
(
api.EgressInfo(
status=EgressStatus.EGRESS_LIMIT_REACHED,
error="Session limit reached",
error_code=413,
details="End reason: Session limit reached",
file_results=[UPLOADED_FILE],
),
RecordingWorkerEvent.LIMIT_REACHED,
),
),
)
def test_to_ended_recording_event_translates_egress_info(egress_info, expected_event):
"""Should tell an egress cut short with a partial file apart from the others."""
assert to_ended_recording_event(egress_info) == expected_event
@pytest.mark.parametrize(
("mode", "egress_info_kwargs", "notification_type"),
(
("transcript", ROOM_CONNECTION_FAILED, "transcriptionInterrupted"),
("transcript", CPU_EXHAUSTED_DRAINED, "transcriptionInterrupted"),
("screen_recording", CPU_EXHAUSTED_DRAINED, "screenRecordingInterrupted"),
("screen_recording", CPU_EXHAUSTED_KILLED, "screenRecordingInterrupted"),
),
)
@mock.patch(
"core.recording.services.recording_events.notification_service."
"notify_external_services"
)
@mock.patch("core.utils.notify_participants")
@mock.patch("core.services.room_management.RoomManagement.update_metadata")
def test_handle_egress_ended_interrupted_saves_partial_recording( # noqa: PLR0913, PLR0917
mock_update_metadata,
mock_notify,
mock_notify_external_services,
mode,
egress_info_kwargs,
notification_type,
service,
caplog,
): # pylint: disable=too-many-arguments,too-many-positional-arguments
"""Should save an interrupted recording and tell participants it is partial."""
mock_notify_external_services.return_value = True
recording = RecordingFactory(worker_id="worker-1", mode=mode, status="active")
data = api.WebhookEvent(
event="egress_ended",
egress_info=api.EgressInfo(
egress_id=recording.worker_id,
file_results=[UPLOADED_FILE],
**egress_info_kwargs,
),
)
with caplog.at_level(logging.WARNING):
service._handle_egress_ended(data)
mock_notify.assert_called_once_with(
room_name=str(recording.room.id), notification_data={"type": notification_type}
)
mock_notify_external_services.assert_called_once_with(recording)
mock_update_metadata.assert_called_once_with(
str(recording.room.id), remove_keys=["recording_mode", "recording_status"]
)
assert (
f"Recording worker reported interrupted for recording {recording.id}"
in caplog.text
)
recording.refresh_from_db()
assert recording.status == "notification_succeeded"
@mock.patch(
"core.recording.services.recording_events.notification_service."
"notify_external_services"
)
@mock.patch("core.utils.notify_participants")
@mock.patch("core.services.room_management.RoomManagement.update_metadata")
def test_handle_egress_ended_interrupted_after_stop_does_not_notify_participants(
mock_update_metadata, mock_notify, mock_notify_external_services, service
):
"""Shouldn't notify participants about a recording they already stopped."""
mock_notify_external_services.return_value = False
recording = RecordingFactory(worker_id="worker-1", status="stopped")
data = api.WebhookEvent(
event="egress_ended",
egress_info=api.EgressInfo(
egress_id=recording.worker_id,
file_results=[UPLOADED_FILE],
**CPU_EXHAUSTED_DRAINED,
),
)
service._handle_egress_ended(data)
mock_notify.assert_not_called()
mock_notify_external_services.assert_called_once_with(recording)
recording.refresh_from_db()
assert recording.status == "saved"
@mock.patch("core.utils.notify_participants")
@mock.patch("core.services.room_management.RoomManagement.update_metadata")
def test_handle_egress_ended_failed_on_another_error_discards_recording(
mock_update_metadata, mock_notify, service
):
"""Should still flag as failed an egress that failed for another reason."""
recording = RecordingFactory(
worker_id="worker-1", mode="transcript", status="active"
)
data = api.WebhookEvent(
event="egress_ended",
egress_info=api.EgressInfo(
egress_id=recording.worker_id,
status=EgressStatus.EGRESS_FAILED,
error="pipeline frozen",
error_code=500,
details="End reason: Source closed",
file_results=[UPLOADED_FILE],
),
)
service._handle_egress_ended(data)
mock_notify.assert_called_once_with(
room_name=str(recording.room.id),
notification_data={"type": "transcriptionFailed"},
)
recording.refresh_from_db()
assert recording.status == "failed"
@mock.patch("core.services.room_management.RoomManagement.update_metadata")
def test_handle_egress_updated_ignores_unmapped_status(mock_update_metadata, service):
"""Shouldn't touch the room's metadata when the egress status is unknown."""
@@ -0,0 +1,40 @@
"""Test the LiveKit reconnection settings exposed by the config endpoint."""
import pytest
from rest_framework.test import APIClient
pytestmark = pytest.mark.django_db
def test_api_config_retrieve_livekit_reconnect_defaults():
"""Without any override, the frontend gets the default backoff."""
response = APIClient().get("/api/v1.0/config/")
assert response.status_code == 200
assert response.json()["livekit"]["reconnect"] == {
"backoff_enabled": True,
"base_delay_ms": 1000,
"max_delay_ms": 10000,
"give_up_after_ms": 180000,
"backoff_reset_ms": 120000,
}
def test_api_config_retrieve_livekit_reconnect_settings(settings):
"""Each reconnection setting reaches the frontend unchanged."""
settings.LIVEKIT_RECONNECT_BACKOFF_ENABLED = False
settings.LIVEKIT_RECONNECT_BASE_DELAY_MS = 500
settings.LIVEKIT_RECONNECT_MAX_DELAY_MS = 30000
settings.LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS = 600000
settings.LIVEKIT_RECONNECT_BACKOFF_RESET_MS = 60000
response = APIClient().get("/api/v1.0/config/")
assert response.status_code == 200
assert response.json()["livekit"]["reconnect"] == {
"backoff_enabled": False,
"base_delay_ms": 500,
"max_delay_ms": 30000,
"give_up_after_ms": 600000,
"backoff_reset_ms": 60000,
}
@@ -184,7 +184,7 @@ def test_api_rooms_list_with_application_disabled(mock_rs_authenticate, settings
@responses.activate
def test_api_rooms_list_with_invalid_rs_token(settings):
"""Listing rooms with invalid resource server token should return 400."""
"""Listing rooms with invalid resource server token should return 401."""
settings.OIDC_OP_INTROSPECTION_ENDPOINT = "https://oidc.example.com/introspect"
settings.OIDC_OP_URL = "https://oidc.example.com"
@@ -202,9 +202,8 @@ def test_api_rooms_list_with_invalid_rs_token(settings):
client.credentials(HTTP_AUTHORIZATION="Bearer invalid-token-123")
response = client.get("/external-api/v1.0/rooms/")
# Return 400 instead of 401 because ResourceServerAuthentication raises
# SuspiciousOperation when the introspected user is not active
assert response.status_code == 400
assert response.status_code == 401
assert "introspected user is not active" in str(response.data).lower()
def test_api_rooms_list_missing_scope():
@@ -422,7 +421,7 @@ def test_api_rooms_retrieve_with_expired_token(settings):
@responses.activate
def test_api_rooms_retrieve_with_invalid_rs_token(settings):
"""Retrieving rooms with invalid resource server token should return 400."""
"""Retrieving rooms with invalid resource server token should return 401."""
settings.OIDC_OP_INTROSPECTION_ENDPOINT = "https://oidc.example.com/introspect"
settings.OIDC_OP_URL = "https://oidc.example.com"
@@ -443,9 +442,8 @@ def test_api_rooms_retrieve_with_invalid_rs_token(settings):
client.credentials(HTTP_AUTHORIZATION="Bearer invalid-token-123")
response = client.get(f"/external-api/v1.0/rooms/{room.id}/")
# Return 400 instead of 401 because ResourceServerAuthentication raises
# SuspiciousOperation when the introspected user is not active
assert response.status_code == 400
assert response.status_code == 401
assert "introspected user is not active" in str(response.data).lower()
def test_api_rooms_retrieve_requires_scope():
@@ -647,7 +645,7 @@ def test_api_rooms_create_with_expired_token(settings):
@responses.activate
def test_api_rooms_create_with_invalid_rs_token(settings):
"""Creating rooms with invalid resource server token should return 400."""
"""Creating rooms with invalid resource server token should return 401."""
settings.OIDC_OP_INTROSPECTION_ENDPOINT = "https://oidc.example.com/introspect"
settings.OIDC_OP_URL = "https://oidc.example.com"
@@ -665,9 +663,8 @@ def test_api_rooms_create_with_invalid_rs_token(settings):
client.credentials(HTTP_AUTHORIZATION="Bearer invalid-token-123")
response = client.post("/external-api/v1.0/rooms/")
# Return 400 instead of 401 because ResourceServerAuthentication raises
# SuspiciousOperation when the introspected user is not active
assert response.status_code == 400
assert response.status_code == 401
assert "introspected user is not active" in str(response.data).lower()
def test_api_rooms_create_inactive_user():
@@ -2971,6 +2968,36 @@ def test_api_rooms_missing_token_is_audited_as_denial(audit_events):
assert event["url"]["path"] == "/external-api/v1.0/rooms/"
@responses.activate
def test_api_rooms_invalid_rs_token_is_audited_as_denial(audit_events, settings):
"""A token refused by the identity provider is recorded as a denial."""
settings.OIDC_OP_INTROSPECTION_ENDPOINT = "https://oidc.example.com/introspect"
settings.OIDC_OP_URL = "https://oidc.example.com"
responses.add(
responses.POST,
"https://oidc.example.com/introspect",
json={"iss": "https://oidc.example.com", "active": False},
)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION="Bearer invalid-token-123")
response = client.get("/external-api/v1.0/rooms/")
assert response.status_code == 401
assert response.headers["WWW-Authenticate"] == "Bearer"
responses.assert_call_count("https://oidc.example.com/introspect", 1)
[event] = find_events(audit_events, "room.list")
assert event["event"]["category"] == ["api", "authentication"]
assert event["event"]["reason"] == "authentication_failed"
assert event["lasuite"]["outcome"] == "denied"
assert event["lasuite"]["actor"] == {"type": "anonymous"}
assert event["http"]["response"] == {"status_code": 401}
assert event["error"]["message"] == response.json()["detail"]
def test_api_rooms_missing_scope_is_audited_as_denial(audit_events):
"""A token without the required scope is a permission denial by the application."""
user = UserFactory()
@@ -0,0 +1,24 @@
"""Unit tests for the value of the LiveKit reconnection delay settings."""
import pytest
from meet.settings import StrictlyPositiveIntegerValue
@pytest.mark.parametrize("raw,expected", [("1", 1), (" 15000 ", 15000)])
def test_strictly_positive_integer_value_parses(raw, expected):
"""A delay above 0 is read as an integer."""
assert StrictlyPositiveIntegerValue(environ=False).to_python(raw) == expected
def test_strictly_positive_integer_value_rejects_zero():
"""0 stops the settings module loading, unlike with PositiveIntegerValue."""
with pytest.raises(ValueError, match="greater than 0"):
StrictlyPositiveIntegerValue(environ=False).to_python("0")
@pytest.mark.parametrize("raw", ["-1", "1e3", "1000ms", ""])
def test_strictly_positive_integer_value_rejects_invalid(raw):
"""A negative or non-integer value stops the settings module loading."""
with pytest.raises(ValueError):
StrictlyPositiveIntegerValue(environ=False).to_python(raw)
@@ -0,0 +1,38 @@
"""
Test utils.is_uuid
"""
import pytest
from faker import Faker
from core import utils
fake = Faker()
UUID = fake.uuid4()
@pytest.mark.parametrize(
"value",
[
UUID,
UUID.upper(),
UUID.replace("-", ""),
f"{{{UUID}}}",
f"urn:uuid:{UUID}",
],
ids=["canonical", "uppercase", "hex", "braces", "urn"],
)
def test_utils_is_uuid_true(value):
"""Strings that parse as a UUID should be recognized."""
assert utils.is_uuid(value) is True
@pytest.mark.parametrize(
"value",
["", "my-room", "abc-defg-hij", f"{UUID}!", f"_{UUID}", UUID[:-1]],
ids=["empty", "slug", "room-slug", "suffix", "prefix", "truncated"],
)
def test_utils_is_uuid_false(value):
"""Strings that do not parse as a UUID should not be recognized."""
assert utils.is_uuid(value) is False
+10 -1
View File
@@ -16,7 +16,7 @@ import string
from datetime import timedelta
from functools import lru_cache
from typing import List, Optional
from uuid import uuid4
from uuid import UUID, uuid4
from django.conf import settings
from django.core.files.storage import default_storage
@@ -336,6 +336,15 @@ def generate_room_slug():
return "-".join(parts)
def is_uuid(value: str) -> bool:
"""Tell whether a value parses as a UUID."""
try:
UUID(value)
except ValueError:
return False
return True
def detect_mimetype(file_buffer: bytes, filename: str | None = None) -> str:
"""
Detect MIME type using multiple methods for better accuracy.
@@ -708,3 +708,7 @@ msgstr "Deutsch"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Spanisch"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Portugiesisch"
@@ -703,3 +703,7 @@ msgstr "German"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Spanish"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Portuguese"
@@ -705,3 +705,7 @@ msgstr "Alemán"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Español"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Portugués"
@@ -709,3 +709,7 @@ msgstr "Allemand"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Espagnol"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Portugais"
@@ -703,3 +703,7 @@ msgstr "Duits"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Spaans"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Portugees"
@@ -0,0 +1,699 @@
# Portuguese (Brazil) translation for La Suite numérique Meet.
# Copyright (C) 2026 La Suite numérique
# This file is distributed under the same license as the Meet package.
# Contributed by Alan Turing Laboratory (ATLab) - Federal University of Ceará (UFC), Brazil.
# Railton Vasconcelos <railton.vasconcelos14@gmail.com>, 2026.
#
msgid ""
msgstr ""
"Project-Id-Version: lasuite-meet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-02 22:52+0000\n"
"PO-Revision-Date: 2026-11-07 21:00-0300\n"
"Last-Translator: Railton Vasconcelos <railton.vasconcelos14@gmail.com> (ATLab - UFC)\n"
"Language-Team: Alan Turing Laboratory (ATLab) - Federal University of Ceará (UFC)\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: core/admin.py:67
msgid "Personal info"
msgstr "Informações pessoais"
#: core/admin.py:80
msgid "Permissions"
msgstr "Permissões"
#: core/admin.py:92
msgid "Important dates"
msgstr "Datas importantes"
#: core/admin.py:206
msgid "Content"
msgstr "Conteúdo"
#: core/admin.py:217
msgid "Deletion"
msgstr "Exclusão"
#: core/admin.py:226
msgid "Derived info"
msgstr "Informações derivadas"
#: core/admin.py:237
msgid "Timestamps"
msgstr "Carimbos de data e hora"
#: core/admin.py:240
msgid "File preview"
msgstr "Pré-visualização do arquivo"
#: core/admin.py:300 core/admin.py:450
msgid "No owner"
msgstr "Sem proprietário"
#: core/admin.py:303 core/admin.py:453
msgid "Multiple owners"
msgstr "Vários proprietários"
#: core/admin.py:316
msgid "Resend notification to external service"
msgstr "Reenviar notificação ao serviço externo"
#: core/admin.py:339
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Falha ao notificar sobre a gravação %(id)s"
#: core/admin.py:347
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Falha ao notificar sobre a gravação %(id)s: %(error)s"
#: core/admin.py:355
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Notificações enviadas com sucesso para %(count)s gravação(ões)."
#: core/admin.py:363
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s gravação(ões) expirada(s) ignorada(s)."
#: core/admin.py:368
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Marcar gravações selecionadas como 'Falha ao parar'"
#: core/admin.py:386
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s gravação(ões) marcada(s) com sucesso como 'Falha ao parar'."
#: core/admin.py:394
#, python-format
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s gravação(ões) com status inelegível ignorada(s)."
#: core/admin.py:517
msgid "No scopes"
msgstr "Sem escopos"
#: core/admin.py:519
msgid "Scopes"
msgstr "Escopos"
#: core/api/filters.py:25
msgid "Creator is me"
msgstr "Criado por mim"
#: core/api/serializers.py:108
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "Você deve ser administrador ou proprietário de uma sala para adicionar acessos a ela."
#: core/api/serializers.py:560
msgid "This file extension is not allowed."
msgstr "Esta extensão de arquivo não é permitida."
#: core/api/viewsets.py:1268
msgid "You have reached the maximum number of files for this type."
msgstr "Você atingiu o número máximo de arquivos para este tipo."
#: core/models.py:38
msgid "Member"
msgstr "Membro"
#: core/models.py:39
msgid "Administrator"
msgstr "Administrador"
#: core/models.py:40
msgid "Owner"
msgstr "Proprietário"
#: core/models.py:56
msgid "Initiated"
msgstr "Iniciada"
#: core/models.py:57
msgid "Active"
msgstr "Ativa"
#: core/models.py:58
msgid "Stopped"
msgstr "Parada"
#: core/models.py:59
msgid "Saved"
msgstr "Salva"
#: core/models.py:60
msgid "Aborted"
msgstr "Cancelada"
#: core/models.py:61
msgid "Failed to Start"
msgstr "Falha ao iniciar"
#: core/models.py:62
msgid "Failed to Stop"
msgstr "Falha ao parar"
#: core/models.py:63
msgid "Notification succeeded"
msgstr "Notificação enviada com sucesso"
#: core/models.py:66
msgid "External process successful"
msgstr "Processo externo concluído com sucesso"
#: core/models.py:68
msgid "External process failed"
msgstr "Falha no processo externo"
#: core/models.py:97
msgid "SCREEN_RECORDING"
msgstr "GRAVAÇÃO_DE_TELA"
#: core/models.py:98
msgid "TRANSCRIPT"
msgstr "TRANSCRIÇÃO"
#: core/models.py:104
msgid "Public Access"
msgstr "Acesso público"
#: core/models.py:105
msgid "Trusted Access"
msgstr "Acesso confiável"
#: core/models.py:106
msgid "Restricted Access"
msgstr "Acesso restrito"
#: core/models.py:118
msgid "id"
msgstr "id"
#: core/models.py:119
msgid "primary key for the record as UUID"
msgstr "chave primária do registro como UUID"
#: core/models.py:125
msgid "created on"
msgstr "criado em"
#: core/models.py:126
msgid "date and time at which a record was created"
msgstr "data e hora em que o registro foi criado"
#: core/models.py:131
msgid "updated on"
msgstr "atualizado em"
#: core/models.py:132
msgid "date and time at which a record was last updated"
msgstr "data e hora da última atualização do registro"
#: core/models.py:150
msgid "sub"
msgstr "sub"
#: core/models.py:152
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Printable ASCII characters only."
msgstr ""
"Opcional para usuários pendentes; obrigatório na ativação da conta. Até 255 "
"caracteres. Apenas caracteres ASCII imprimíveis."
#: core/validators.py:22
msgid "Enter a valid sub. This value should be printable ASCII only."
msgstr "Insira um sub válido. Este valor deve conter apenas caracteres ASCII imprimíveis."
#: core/models.py:161
msgid "identity email address"
msgstr "endereço de e-mail de identidade"
#: core/models.py:166
msgid "admin email address"
msgstr "endereço de e-mail administrativo"
#: core/models.py:168
msgid "full name"
msgstr "nome completo"
#: core/models.py:170
msgid "short name"
msgstr "nome curto"
#: core/models.py:176
msgid "language"
msgstr "idioma"
#: core/models.py:177
msgid "The language in which the user wants to see the interface."
msgstr "O idioma no qual o usuário deseja visualizar a interface."
#: core/models.py:183
msgid "The timezone in which the user wants to see times."
msgstr "O fuso horário no qual o usuário deseja visualizar os horários."
#: core/models.py:190
msgid "default room access level"
msgstr "nível de acesso padrão da sala"
#: core/models.py:192
msgid ""
"Access level applied by default to new rooms created by this user. When "
"empty, the instance default is used."
msgstr ""
"Nível de acesso aplicado por padrão às novas salas criadas por este usuário. "
"Quando vazio, o padrão da instância é utilizado."
#: core/models.py:199
msgid "default room configuration"
msgstr "configuração padrão da sala"
#: core/models.py:201
msgid "Configurations applied by default to new rooms created by this user."
msgstr "Configurações aplicadas por padrão às novas salas criadas por este usuário."
#: core/models.py:205
msgid "device"
msgstr "dispositivo"
#: core/models.py:207
msgid "Whether the user is a device or a real user."
msgstr "Define se o usuário é um dispositivo ou um usuário real."
#: core/models.py:210
msgid "staff status"
msgstr "status da equipe"
#: core/models.py:212
msgid "Whether the user can log into this admin site."
msgstr "Define se o usuário pode acessar este painel de administração."
#: core/models.py:215
msgid "active"
msgstr "ativo"
#: core/models.py:218
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
msgstr ""
"Define se este usuário deve ser tratado como ativo. Desmarque esta opção em "
"vez de excluir contas."
#: core/models.py:231
msgid "user"
msgstr "usuário"
#: core/models.py:232
msgid "users"
msgstr "usuários"
#: core/models.py:298
msgid "Resource"
msgstr "Recurso"
#: core/models.py:299
msgid "Resources"
msgstr "Recursos"
#: core/models.py:357
msgid "Resource access"
msgstr "Acesso a recurso"
#: core/models.py:358
msgid "Resource accesses"
msgstr "Acessos a recursos"
#: core/models.py:364
msgid "Resource access with this User and Resource already exists."
msgstr "Já existe um acesso para este usuário e este recurso."
#: core/models.py:421
msgid "Visio room configuration"
msgstr "Configuração da sala de videoconferência"
#: core/models.py:422
msgid "Values for Visio parameters to configure the room."
msgstr "Valores dos parâmetros de videoconferência para configurar a sala."
#: core/models.py:429
msgid "Room PIN code"
msgstr "Código PIN da sala"
#: core/models.py:430
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr "Código único de n dígitos que identifica esta sala no modo telefonia."
#: core/models.py:436 core/models.py:597
msgid "Room"
msgstr "Sala"
#: core/models.py:437
msgid "Rooms"
msgstr "Salas"
#: core/models.py:608
msgid "Worker ID"
msgstr "ID do worker"
#: core/models.py:610
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
msgstr ""
"Insira um identificador para a gravação do worker. Este ID é mantido mesmo "
"quando o worker para, facilitando o rastreamento."
#: core/models.py:618
msgid "Recording mode"
msgstr "Modo de gravação"
#: core/models.py:619
msgid "Defines the mode of recording being called."
msgstr "Define o modo de gravação utilizado."
#: core/models.py:624 core/models.py:625
msgid "Recording options"
msgstr "Opções de gravação"
#: core/models.py:632
msgid "External Process ID"
msgstr "ID do processo externo"
#: core/models.py:633
msgid "ID of the external process associated with the recording."
msgstr "ID do processo externo associado à gravação."
#: core/models.py:639
msgid "Recording"
msgstr "Gravação"
#: core/models.py:640
msgid "Recordings"
msgstr "Gravações"
#: core/models.py:750
msgid "Recording/user relation"
msgstr "Relação gravação/usuário"
#: core/models.py:751
msgid "Recording/user relations"
msgstr "Relações gravação/usuário"
#: core/models.py:757
msgid "This user is already in this recording."
msgstr "Este usuário já está nesta gravação."
#: core/models.py:763
msgid "This team is already in this recording."
msgstr "Esta equipe já está nesta gravação."
#: core/models.py:769
msgid "Either user or team must be set, not both."
msgstr "Deve ser definido o usuário ou a equipe, mas não ambos."
#: core/models.py:786
msgid "Create rooms"
msgstr "Criar salas"
#: core/models.py:787
msgid "List rooms"
msgstr "Listar salas"
#: core/models.py:788
msgid "Retrieve room details"
msgstr "Obter detalhes da sala"
#: core/models.py:789
msgid "Update rooms"
msgstr "Atualizar salas"
#: core/models.py:790
msgid "Delete rooms"
msgstr "Excluir salas"
#: core/models.py:803
msgid "Application name"
msgstr "Nome da aplicação"
#: core/models.py:804
msgid "Descriptive name for this application."
msgstr "Nome descritivo para esta aplicação."
#: core/models.py:814
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Criptografado ao salvar. Copie agora se este for um novo segredo."
#: core/models.py:825
msgid "Application"
msgstr "Aplicação"
#: core/models.py:826
msgid "Applications"
msgstr "Aplicações"
#: core/models.py:849
msgid "Enter a valid domain"
msgstr "Insira um domínio válido"
#: core/models.py:852
msgid "Domain"
msgstr "Domínio"
#: core/models.py:853
msgid "Email domain this application can act on behalf of."
msgstr "Domínio de e-mail em nome do qual esta aplicação pode atuar."
#: core/models.py:865
msgid "Application domain"
msgstr "Domínio da aplicação"
#: core/models.py:866
msgid "Application domains"
msgstr "Domínios da aplicação"
#: core/models.py:884
msgid "Pending"
msgstr "Pendente"
#: core/models.py:885
msgid "Analyzing"
msgstr "Analisando"
#: core/models.py:892
msgid "Ready"
msgstr "Pronto"
#: core/models.py:898
msgid "Background image"
msgstr "Imagem de fundo"
#: core/models.py:910
msgid "title"
msgstr "título"
#: core/models.py:934
msgid "Malware detection info when the analysis status is unsafe."
msgstr "Informações de detecção de malware quando o status da análise não for seguro."
#: core/models.py:939
msgid "File"
msgstr "Arquivo"
#: core/models.py:940
msgid "Files"
msgstr "Arquivos"
#: core/models.py:1060
msgid "This file is already hard deleted."
msgstr "Este arquivo já foi excluído permanentemente."
#: core/models.py:1070
msgid "To hard delete a file, it must first be soft deleted."
msgstr "Para excluir permanentemente um arquivo, ele deve primeiro ser marcado para exclusão."
#: core/recording/event/notification.py:124
msgid "Your recording is ready"
msgstr "Sua gravação está pronta"
#: core/recording/event/notification.py:195
msgid "Transcription"
msgstr "Transcrição"
#: core/recording/event/notification.py:206
#, python-brace-format
msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
msgstr "Reunião \"{room}\" em {room_recording_date} às {room_recording_time}"
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Videochamada em andamento: {sender.email} está aguardando você se conectar"
#: core/templates/mail/html/invitation.html:151
#: core/templates/mail/html/screen_recording.html:151
#: core/templates/mail/text/invitation.txt:4
#: core/templates/mail/text/screen_recording.txt:4
msgid "Logo email"
msgstr "Logotipo do e-mail"
#: core/templates/mail/html/invitation.html:181
#: core/templates/mail/text/invitation.txt:6
msgid "invites you to join an ongoing video call"
msgstr "convida você para participar de uma videochamada em andamento"
#: core/templates/mail/html/invitation.html:192
#: core/templates/mail/text/invitation.txt:8
msgid "JOIN THE CALL"
msgstr "ENTRAR NA CHAMADA"
#: core/templates/mail/html/invitation.html:219
#: core/templates/mail/text/invitation.txt:14
msgid ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
msgstr ""
"Se não conseguir clicar no botão, copie e cole a URL no seu navegador para "
"entrar na chamada."
#: core/templates/mail/html/invitation.html:227
#: core/templates/mail/text/invitation.txt:16
msgid "Tips for a better experience:"
msgstr "Dicas para uma melhor experiência:"
#: core/templates/mail/html/invitation.html:229
#: core/templates/mail/text/invitation.txt:18
msgid "Use Chrome or Firefox for better call quality"
msgstr "Use o Chrome ou o Firefox para melhor qualidade de chamada"
#: core/templates/mail/html/invitation.html:230
#: core/templates/mail/text/invitation.txt:19
msgid "Test your microphone and camera before joining"
msgstr "Teste seu microfone e sua câmera antes de entrar"
#: core/templates/mail/html/invitation.html:231
#: core/templates/mail/text/invitation.txt:20
msgid "Make sure you have a stable internet connection"
msgstr "Certifique-se de ter uma conexão de internet estável"
#: core/templates/mail/html/invitation.html:240
#: core/templates/mail/html/screen_recording.html:237
#: core/templates/mail/text/invitation.txt:22
#: core/templates/mail/text/screen_recording.txt:24
#, python-format
msgid " Thank you for using %(brandname)s. "
msgstr " Obrigado por usar o %(brandname)s. "
#: core/templates/mail/html/invitation.html:271
#, python-format
msgid ""
"This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
msgstr ""
"Este e-mail foi enviado para %(email)s por <a href=\"%(href)s\">%(name)s</a>"
#: core/templates/mail/html/screen_recording.html:180
#: core/templates/mail/text/screen_recording.txt:7
msgid "Your recording is ready!"
msgstr "Sua gravação está pronta!"
#: core/templates/mail/html/screen_recording.html:187
#: core/templates/mail/text/screen_recording.txt:9
#, python-format
msgid ""
" Your recording of \"%(room_name)s\" on %(recording_date)s at "
"%(recording_time)s is now ready to download. "
msgstr ""
" Sua gravação de \"%(room_name)s\" em %(recording_date)s às "
"%(recording_time)s já está pronta para download. "
#: core/templates/mail/html/screen_recording.html:187
#: core/templates/mail/text/screen_recording.txt:9
#, python-format
msgid " The recording will expire in %(days)s days. "
msgstr " A gravação expirará em %(days)s dias. "
#: core/templates/mail/html/screen_recording.html:192
#: core/templates/mail/text/screen_recording.txt:10
msgid ""
" Sharing the recording via link is not yet available. Only organizers can "
"download it. "
msgstr ""
" O compartilhamento da gravação por link ainda não está disponível. Apenas "
"os organizadores podem baixá-la. "
#: core/templates/mail/html/screen_recording.html:198
#: core/templates/mail/text/screen_recording.txt:12
msgid "To keep this recording permanently:"
msgstr "Para manter esta gravação permanentemente:"
#: core/templates/mail/html/screen_recording.html:200
#, python-format
msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
msgstr "Clique no link \"<a href=\"%(link)s\">Abrir</a>\" abaixo "
#: core/templates/mail/html/screen_recording.html:201
#: core/templates/mail/text/screen_recording.txt:15
msgid "Use the \"Download\" button in the interface "
msgstr "Use o botão \"Baixar\" na interface "
#: core/templates/mail/html/screen_recording.html:202
#: core/templates/mail/text/screen_recording.txt:16
msgid "Save the file to your preferred location"
msgstr "Salve o arquivo no local de sua preferência"
#: core/templates/mail/html/screen_recording.html:213
#: core/templates/mail/text/screen_recording.txt:18
msgid "Open"
msgstr "Abrir"
#: core/templates/mail/html/screen_recording.html:222
#: core/templates/mail/text/screen_recording.txt:20
#, python-format
msgid ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
msgstr ""
" Se tiver alguma dúvida ou precisar de ajuda, entre em contato com nossa "
"equipe de suporte em %(support_email)s. "
#: core/templates/mail/text/invitation.txt:24
#, python-format
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
msgstr "Este e-mail foi enviado para %(email)s por %(name)s [%(href)s]"
#: core/templates/mail/text/screen_recording.txt:14
#, python-format
msgid "Click the \"Open [%(link)s]\" link below "
msgstr "Clique no link \"Abrir [%(link)s]\" abaixo "
#: meet/settings.py:238
msgid "English"
msgstr "Inglês"
#: meet/settings.py:239
msgid "French"
msgstr "Francês"
#: meet/settings.py:240
msgid "Dutch"
msgstr "Holandês"
#: meet/settings.py:241
msgid "German"
msgstr "Alemão"
#: meet/settings.py:242
msgid "Spanish"
msgstr "Espanhol"
#: meet/settings.py:243
msgid "Portuguese"
msgstr "Português"
+34
View File
@@ -68,6 +68,17 @@ class VideoCodecValue(values.Value):
return codec
class StrictlyPositiveIntegerValue(values.PositiveIntegerValue):
"""An integer greater than 0"""
def to_python(self, value):
"""Ensure the integer is greater than 0."""
int_value = super().to_python(value)
if int_value == 0:
raise ValueError(f"Expected an integer greater than 0, got {value!r}.")
return int_value
class ResolutionSpec(BaseModel):
"""An value of RECORDING_ENCODING_AVAILABLE_RESOLUTIONS."""
@@ -291,6 +302,7 @@ class Base(Configuration):
("nl-nl", _("Dutch")),
("de-de", _("German")),
("es-es", _("Spanish")),
("pt-br", _("Portuguese")),
)
)
@@ -787,6 +799,28 @@ class Base(Configuration):
LIVEKIT_DEFAULT_VIDEO_CODEC = VideoCodecValue(
"vp9", environ_name="LIVEKIT_DEFAULT_VIDEO_CODEC", environ_prefix=None
)
# Livekit websocket reconnection exponential backoff config sent to the frontend.
# Before its first reconnection attempt, a client waits a random delay up to the base
# delay. That ceiling doubles after every failed attempt, capped at the max delay, and
# later delays fall between half the ceiling and the ceiling.
# It gives up after the give-up delay unless the media still flows.
# After a successful reconnection, a new disconnection starting sooner than the reset
# delay after the last one resumes the backoff one step above where that one stopped.
LIVEKIT_RECONNECT_BACKOFF_ENABLED = values.BooleanValue(
True, environ_name="LIVEKIT_RECONNECT_BACKOFF_ENABLED", environ_prefix=None
)
LIVEKIT_RECONNECT_BASE_DELAY_MS = StrictlyPositiveIntegerValue(
1000, environ_name="LIVEKIT_RECONNECT_BASE_DELAY_MS", environ_prefix=None
)
LIVEKIT_RECONNECT_MAX_DELAY_MS = StrictlyPositiveIntegerValue(
10000, environ_name="LIVEKIT_RECONNECT_MAX_DELAY_MS", environ_prefix=None
)
LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS = StrictlyPositiveIntegerValue(
180000, environ_name="LIVEKIT_RECONNECT_GIVE_UP_AFTER_MS", environ_prefix=None
)
LIVEKIT_RECONNECT_BACKOFF_RESET_MS = values.PositiveIntegerValue(
120000, environ_name="LIVEKIT_RECONNECT_BACKOFF_RESET_MS", environ_prefix=None
)
CONNECTION_TEST_ENABLED = values.BooleanValue(
environ_name="CONNECTION_TEST_ENABLED",
environ_prefix=None,
+23 -23
View File
@@ -2,12 +2,12 @@
# Meet package
#
[build-system]
requires = ["uv_build>=0.12.6,<0.13.0"]
requires = ["uv_build>=0.13.5,<0.14.0"]
build-backend = "uv_build"
[project]
name = "meet"
version = "1.36.1"
version = "1.37.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -24,48 +24,48 @@ keywords = ["Django", "Contacts", "Templates", "RBAC"]
license = "MIT"
requires-python = ">=3.13"
dependencies = [
"boto3==1.43.80",
"Brotli==1.2.0",
"boto3==1.43.118",
"Brotli==1.2.1",
"brevo-python==1.2.0",
"celery[redis]==5.6.3",
"dj-database-url==3.1.2",
"django-configurations==2.5.1",
"django-cors-headers==4.9.0",
"django-countries==9.0.0",
"django-filter==26.1",
"django-lasuite[all]==0.0.29",
"django-countries==9.1.0",
"django-filter==26.2",
"django-lasuite[all]==1.0.0",
"django-parler==2.4",
"redis==5.2.1",
"django-redis==7.0.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django-pydantic-field==0.5.4",
"django-pydantic-field==0.5.6",
"django==5.2.17",
"djangorestframework==3.18.0",
"djangorestframework==3.18.3",
"drf_spectacular==0.30.0",
"dockerflow==2026.3.4",
"easy_thumbnails==2.10.1",
"factory_boy==3.3.3",
"gunicorn==26.2.0",
"jsonschema==4.26.0",
"markdown==3.10.3",
"markdown==3.11.2",
"menshen-client==0.2.0",
"nested-multipart-parser==1.6.0",
"posthog==7.44.0",
"psycopg[binary,pool]==3.3.4",
"pydantic==2.13.4",
"PyJWT==2.14.0",
"posthog==7.75.11",
"psycopg[binary,pool]==3.3.6",
"pydantic==2.14.1",
"PyJWT==2.15.1",
"python-frontmatter==1.3.0",
"python-magic==0.4.27",
"requests==2.34.2",
"sentry-sdk==2.68.1",
"sentry-sdk==2.73.0",
"whitenoise==6.12.0",
"mozilla-django-oidc==5.0.2",
"livekit-api==1.2.0",
"livekit-api==1.2.2",
"aiohttp==3.14.5",
"urllib3==2.8.0",
"phonenumbers==9.0.37",
"cryptography==50.0.1", # CVE-2026-69247
"phonenumbers==9.0.41",
"cryptography==50.0.2", # CVE-2026-69247
"pillow==13.0.0", # CVE-2026-112403
]
@@ -78,10 +78,10 @@ dependencies = [
[dependency-groups]
dev = [
"django-extensions==4.1",
"drf-spectacular-sidecar==2026.8.1",
"drf-spectacular-sidecar==2026.10.1",
"freezegun==1.5.5",
"ipdb==0.13.13",
"ipython==9.16.1",
"ipython==9.17.1",
"pyfakefs==6.2.0",
"pylint-django==2.8.0",
"pylint==4.1.3",
@@ -90,9 +90,9 @@ dev = [
"pytest==9.1.1",
"pytest-icdiff==0.9",
"pytest-xdist==3.8.0",
"responses==0.26.2",
"ruff==0.16.4",
"types-requests==2.33.0.20260712",
"responses==0.26.3",
"ruff==0.17.2",
"types-requests==2.33.0.20261006",
]
[tool.uv.build-backend]
+325 -238
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 5
requires-python = ">=3.13"
[[package]]
@@ -223,30 +223,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.43.80"
version = "1.43.118"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cc/97/9ef9b949f253b197c813c57ab58578572d20dbacedceb5a5f4516ea628cb/boto3-1.43.80.tar.gz", hash = "sha256:384d522e2ce722266afceea8ab3e618e16695b358ebe32b45e0a2109fcd53a28", size = 112674, upload-time = "2026-08-25T20:38:34.564Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/8b/5694c8433b5202f34a82def4edee274f7df1eba2919c66d9d389d2202ec8/boto3-1.43.118.tar.gz", hash = "sha256:c0620f799e0f0c222ba42c337b65cdbcd3da38a3121187f634d8926cbbb28a9c", size = 112649, upload-time = "2026-10-22T19:22:09.629Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/66/415da75a7846e46156a4746aae72a8e4b0c4d263e2c2559a083ad247ba39/boto3-1.43.80-py3-none-any.whl", hash = "sha256:26afe3b950ca1cc722c7fa5544991c222919c98355f82300a55b6315a2c4995e", size = 140024, upload-time = "2026-08-25T20:38:33.021Z" },
{ url = "https://files.pythonhosted.org/packages/bb/a3/65bb3d64928f3c2da17640b92044084150317f02adf22442cb0c52af2a8b/boto3-1.43.118-py3-none-any.whl", hash = "sha256:ab8f3d2c622ef039338b4f228d97ea55f1abd573d84bb9b9a5ee8cb6780c0ccd", size = 140045, upload-time = "2026-10-22T19:22:08.353Z" },
]
[[package]]
name = "botocore"
version = "1.43.104"
version = "1.43.123"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/47/5825401cb9883bc0fbf981dfb4893371f48bc77527bde6c1efe8f9aa8754/botocore-1.43.104.tar.gz", hash = "sha256:099f84876df9f6dc23a24b4d8c02da2d73c6949249761dd85585f735b825c182", size = 16254091, upload-time = "2026-09-28T19:32:10.964Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/de/011fc2a47fb066c47064b122498d2fd21f55bc90ea514acbddc37ca0f8c1/botocore-1.43.123.tar.gz", hash = "sha256:80948cb9b1cdf1a1a25809089a42e3a1bc4af0258d198222db6e97dcc660dd0c", size = 16486269, upload-time = "2026-10-30T19:18:44.609Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/e9/88407daa4bedcb7c6e01b6373c2f78374775ed12557aa768c6d88f2fb78d/botocore-1.43.104-py3-none-any.whl", hash = "sha256:a447978a815f8ac7ef2f4ba103b6a0c310e56536386974df769edb1326f3b208", size = 15947058, upload-time = "2026-09-28T19:32:08.243Z" },
{ url = "https://files.pythonhosted.org/packages/db/96/4f2dffd304425c07160ab2c12cb13322d852fd956fe9d4386228c93f02ea/botocore-1.43.123-py3-none-any.whl", hash = "sha256:e30580f9c1282c321529238ff8ff75066096a2adbd0a293c8ae592c21d1733a3", size = 16185987, upload-time = "2026-10-30T19:18:39.794Z" },
]
[[package]]
@@ -263,30 +263,49 @@ sdist = { url = "https://files.pythonhosted.org/packages/a8/13/9c5fcc8be41af6827
[[package]]
name = "brotli"
version = "1.2.0"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" }
sdist = { url = "https://files.pythonhosted.org/packages/cf/81/1546c983678a9b4e77cb4718eaedd1b2caf24a6cdbf1eb8775938d22aff7/brotli-1.2.1.tar.gz", hash = "sha256:02b2f7b5a83485efe448a44ddf7983def05c3bb1dcf917d4f7154070260e13de", size = 7389447, upload-time = "2026-10-22T14:00:12.66Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" },
{ url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" },
{ url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" },
{ url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" },
{ url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" },
{ url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" },
{ url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" },
{ url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" },
{ url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" },
{ url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" },
{ url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" },
{ url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" },
{ url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" },
{ url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" },
{ url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" },
{ url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" },
{ url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" },
{ url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" },
{ url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" },
{ url = "https://files.pythonhosted.org/packages/f0/85/d74a573b92ba449960eb088ce87dcb61a9dc6a0c5a7ba8d104ae180e64c3/brotli-1.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a84e0ae14d1efe782a9f143021c4e01d5ed986a140fe5070505cc5978eaf0592", size = 861657, upload-time = "2026-10-22T13:58:10.392Z" },
{ url = "https://files.pythonhosted.org/packages/ca/bd/848b55fbd96fa1ce9f534aa39c930f864dbf224282f4a3808c0d35ea2819/brotli-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c5302747f969d5ec925810176f7697712a3fa2ea0fae0c7a85aa4ad9325247fd", size = 442252, upload-time = "2026-10-22T13:58:11.647Z" },
{ url = "https://files.pythonhosted.org/packages/40/41/95d5e6774cb037851001038484bcf98b3af203664fe78d29dd03ece317e7/brotli-1.2.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:7ec678c52c3ef5e37482a094d109c04cf9e4a0e2923dd1e0fbddb4bf133a976e", size = 1242773, upload-time = "2026-10-22T13:58:13.182Z" },
{ url = "https://files.pythonhosted.org/packages/8b/ad/0942abad71d8b2aabd21472efa5be31ab0be31f98271d58767b9797eec82/brotli-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b5b458950312830913f837e179ce9221000ab7a20b4e42d7b48c1fcd79140de1", size = 1402440, upload-time = "2026-10-22T13:58:14.513Z" },
{ url = "https://files.pythonhosted.org/packages/a3/53/2f7e666e61a1419d527904b6018a711f10650a851e7767358abf2919625e/brotli-1.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f37ccd159dae35d4914651d57142c8077aa67ab7e3ffef690b00bcb7a7dbd8e6", size = 1424432, upload-time = "2026-10-22T13:58:15.789Z" },
{ url = "https://files.pythonhosted.org/packages/65/85/4c57c3cd2eee42878677b4943086e6b36cefc50214a5447028268045ce63/brotli-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:264fd5b200f6f6170cb551a4b9fcaabf8aa846ddaefe72db4d5f49f9d3743df9", size = 1420480, upload-time = "2026-10-22T13:58:17.007Z" },
{ url = "https://files.pythonhosted.org/packages/8f/f7/65e75560e20d522db1f0c75a1b2612c6ebd542ff9df17b8920cdbe4cde68/brotli-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1329c80daa60752f1d94f507479504921e358e71746c5fce9b55b3d51ac038bc", size = 1485998, upload-time = "2026-10-22T13:58:18.422Z" },
{ url = "https://files.pythonhosted.org/packages/5d/2a/6cc0cfc4170bd72f6fc0aef11984e7c583ecf0a7fec563bb4292c0d50e15/brotli-1.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:77ef5882c5318ffc3dd94c6abc6a8f1ec2af75d66522319f1a250a07d53d0546", size = 1279199, upload-time = "2026-10-22T13:58:19.709Z" },
{ url = "https://files.pythonhosted.org/packages/66/36/97f3eefc9295ce8d87b568433d38e0ca3345b82571341196817c96b2ca50/brotli-1.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6bd29ab84bb8972370cacd310e8a91651f54476d01de1cd1a4cf0bece5eb25e1", size = 1594118, upload-time = "2026-10-22T13:58:20.857Z" },
{ url = "https://files.pythonhosted.org/packages/03/61/687b36bd6b714270ff3d7ff96df6199336abc26c1fa98da276d9776d75b7/brotli-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed8e452f5cb34f59f4861d50d029c6203b5f8f11c13d4186264392a64b4def8d", size = 1489530, upload-time = "2026-10-22T13:58:22Z" },
{ url = "https://files.pythonhosted.org/packages/8c/b4/379b2e5584be70110b154021ffa61b84dc3b476d2c031317f5c3fbcfcde5/brotli-1.2.1-cp313-cp313-win32.whl", hash = "sha256:c788fdab2eeeab671f5ab375ad010ef743f6c95be67f60f878e8876a51f6c671", size = 333014, upload-time = "2026-10-22T13:58:23.112Z" },
{ url = "https://files.pythonhosted.org/packages/96/c2/0c7abb25013043d4bcf7dd768d343bf7dee6ce99ac8c172f7ee4aef1c772/brotli-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:e6c4fa55eab6b5684b33239597876a18a35ce1280b63ab960164032afbb8783f", size = 371072, upload-time = "2026-10-22T13:58:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/81/68/5f9d96203fdce3ff9ba1186ce10220e26483ffa2384cde5430dd980e9057/brotli-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:67f4dceb22ce3a742751cee849644449b169684699aa548e77d771de304e963c", size = 380893, upload-time = "2026-10-22T13:58:25.216Z" },
{ url = "https://files.pythonhosted.org/packages/af/dd/641dd6072ea8c5e0ef7d1f8dc02149b4b0b77ef68fe12fc08b08320980d5/brotli-1.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b8b2fc7901b31d5885f26521cf2d74feccdc126a272ba1d879b7545bb25468bc", size = 863184, upload-time = "2026-10-22T13:58:27.201Z" },
{ url = "https://files.pythonhosted.org/packages/b2/b8/3c4a50dc10f098a022519042eab84f11ef8c29abb9270fca76086ac392c3/brotli-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:de1bf30d819809a964060356032e6670c9923644666afaaf3d1719d05ddb844e", size = 443276, upload-time = "2026-10-22T13:58:28.47Z" },
{ url = "https://files.pythonhosted.org/packages/b1/a1/5ef931a404de2c61b2cd775e70b1652ff3f4d434f0589da22efbee590f4a/brotli-1.2.1-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:ed62c15ce5e8b975b2710a0f7c8bb953cbffb472fccb05055be6afcba9520c19", size = 1243026, upload-time = "2026-10-22T13:58:29.934Z" },
{ url = "https://files.pythonhosted.org/packages/11/95/54e7278ee3fc589a6ac3a9bc191b5412542949411f0b38c56614d0f51282/brotli-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3942e2d365f009e5e7839114fa315e1a2bcfea12b52064a28f1a7281862a2a7", size = 1402517, upload-time = "2026-10-22T13:58:31.231Z" },
{ url = "https://files.pythonhosted.org/packages/4d/db/e065253a23a09d97dc7bd8cb47194a85479d71be35a51b5c575ae5095ebe/brotli-1.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6532d2c58018121a9eb5882656cf862cdbbd853600cb5fbccac59bcff14f66b0", size = 1424572, upload-time = "2026-10-22T13:58:32.468Z" },
{ url = "https://files.pythonhosted.org/packages/c4/07/8948582ce173e8e43cea9fadb8c3c3ea94800fb9814119c9239c1ae6c797/brotli-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a998857af4fcd19920bdf5f30f0f1a23f38472a703d33e49168cd4fd32438377", size = 1420557, upload-time = "2026-10-22T13:58:34.1Z" },
{ url = "https://files.pythonhosted.org/packages/c0/d4/65073a06d6356a8f60b4c6ce0c3ade206d1118b8b8705b548f7b72ed549a/brotli-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f352f797b1477c46a20e45f4cbc3a0e258e61963c8ae8f79f84a7bfb4195b37e", size = 1486209, upload-time = "2026-10-22T13:58:35.331Z" },
{ url = "https://files.pythonhosted.org/packages/5a/19/8663beee29665f6fdf520b9c7ea333e0e930952176092be24c4c8bd8f791/brotli-1.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:be371af72af54b6e508b68a7a87768e05b7e13023f6c82a3e3f5e4d0e829e186", size = 1279217, upload-time = "2026-10-22T13:58:36.719Z" },
{ url = "https://files.pythonhosted.org/packages/bb/a6/3851ec19707a1610412ecf4f057d7c1b5acbb8f8b211494a70e1f451ccf6/brotli-1.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dca1bf90b3a6a7440904f128464eb95bf46ac9da7ad3c85912483dd83128fd95", size = 1594292, upload-time = "2026-10-22T13:58:38.115Z" },
{ url = "https://files.pythonhosted.org/packages/8d/13/6bc9d4cb30fb0a475cd149c2075b9f63b93257a84ff70d0783a403f4b015/brotli-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4a7be59d374c445766fa4ac066a04d19a757365ae00a950053d0ab7388d1039", size = 1489719, upload-time = "2026-10-22T13:58:39.365Z" },
{ url = "https://files.pythonhosted.org/packages/04/da/1ec2e9b4f500693e56e6391f128dd0d304d6d2b7f0d5090e80a802b722f1/brotli-1.2.1-cp314-cp314-win32.whl", hash = "sha256:0a30e5df5ba985b67f85e13dfd7e5a68e186d8d9b5cff7d9a8904832bb413e81", size = 338208, upload-time = "2026-10-22T13:58:40.927Z" },
{ url = "https://files.pythonhosted.org/packages/24/5e/80cd38322263da5e92ea4935423f1d3cfa2509201673dce77b772c47b997/brotli-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:972a2e19dbaab47d0aa77c1d07f71c930bdc53538f2b33decc905e92a4be5cbd", size = 376707, upload-time = "2026-10-22T13:58:42.025Z" },
{ url = "https://files.pythonhosted.org/packages/c5/35/cb35c489fcd0a813bbb7500465af5a7bac3bee7d996f6a211bac9096ef2c/brotli-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:a3dea7e1ac49e29306500a8627fa3eacabcc9e238d22495eea2d8e61de77065b", size = 390280, upload-time = "2026-10-22T13:58:43.256Z" },
{ url = "https://files.pythonhosted.org/packages/a2/72/85ea89c1088673a297e8b652cf0080668812144d5525db6512eadddf9d19/brotli-1.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:eea17e3cdc6f9aecffe15fe9a9eeba8e8c049a5d347de1561eed2240cdf35a79", size = 863002, upload-time = "2026-10-22T13:58:44.325Z" },
{ url = "https://files.pythonhosted.org/packages/81/6a/bd4db4ef0c4d083d8a2e5a1d0b20239b186f261e72b14a9a0ec91c078e92/brotli-1.2.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:761db18dc32b24d00fbedca1013089eb3470053cefa8492bd9c07b11102d72bf", size = 443263, upload-time = "2026-10-22T13:58:45.474Z" },
{ url = "https://files.pythonhosted.org/packages/79/1a/08635be453d523474fdfce81f588583a4e8049c75c04df849b5a278bf027/brotli-1.2.1-cp315-cp315-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:035f3bf861328d8b7fcb61f4a976f9913027158e5666116733d59ffc65f2745e", size = 1244379, upload-time = "2026-10-22T13:58:46.615Z" },
{ url = "https://files.pythonhosted.org/packages/3b/94/52f2facc5ae118a68c4f0e715e03c9f854507a359608804d7de2dfe8d603/brotli-1.2.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:aa32b51702a0b6e9fddc0d1dc66cb6ed6af112f1bba0cac2be9039546a862b5c", size = 1404133, upload-time = "2026-10-22T13:58:47.756Z" },
{ url = "https://files.pythonhosted.org/packages/1e/9b/768635c710b684574da55e12bcdbe36419fd8bf6cb97e01c72a4593f9747/brotli-1.2.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:810aea5958d6a2d92593fd27218df528644aeb72cc7f6d401e650752d6c98949", size = 1424875, upload-time = "2026-10-22T13:58:48.912Z" },
{ url = "https://files.pythonhosted.org/packages/46/39/7b7d53d7cea61f35f6b52ea149804523d01e532bf26017eed4351d82a7ad/brotli-1.2.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f00ff4b56caf151365c77988eb85a4a71eb26fe63047d4084d5614e06d65e3cf", size = 1425345, upload-time = "2026-10-22T13:58:50.253Z" },
{ url = "https://files.pythonhosted.org/packages/cb/5e/652c788a4da5c6415c7ae31382626ba870020c9280e0f9be1a6dfec80261/brotli-1.2.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dafaeb38712f99e5c31ad7f02c366f1ddd3792921b94066015f8c49f197163b0", size = 1487925, upload-time = "2026-10-22T13:58:51.593Z" },
{ url = "https://files.pythonhosted.org/packages/85/c9/1b7753d02c106ff70b3d88056586ed7c2fe329d2aa5707cdaebe90372942/brotli-1.2.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:972e34c5cbbfe6c90358bfae38edae1a29d3456e920da5a4664c98f221ee4df8", size = 1276526, upload-time = "2026-10-22T13:58:53.142Z" },
{ url = "https://files.pythonhosted.org/packages/a5/5f/6ae3e85bfb7a3f4711cea5a1b92d4377fddf1319a666cda213cc83b6c040/brotli-1.2.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:59ca34c7a83d7a3b842247183c426f891a95a8f57db41b7aac92195c51e8a83a", size = 1594681, upload-time = "2026-10-22T13:58:54.571Z" },
{ url = "https://files.pythonhosted.org/packages/8a/52/2808406d83e5462c086d69dbebb892f4ed78d2eb4b6755ea4b9e58357e3b/brotli-1.2.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:90bd49511341dd7c40bb3acaa97cd8b9e5df4821a5714b842117e7069c7cc03e", size = 1486968, upload-time = "2026-10-22T13:58:56.008Z" },
{ url = "https://files.pythonhosted.org/packages/a9/53/35042a907c309af8defb423e58692bdc5e3e1fd3d03388892b7614270626/brotli-1.2.1-cp315-cp315-win32.whl", hash = "sha256:1880db6241276e191d3e34a6e4eea708c6629103c396f0ea21e75b0ce63a9a2c", size = 338212, upload-time = "2026-10-22T13:58:57.189Z" },
{ url = "https://files.pythonhosted.org/packages/38/6c/883a32c23603e278ae93d8df31f0573b67382de827e6e05a6e9ee0b51896/brotli-1.2.1-cp315-cp315-win_amd64.whl", hash = "sha256:09a2f112a638aca15a88f298571788795fc45d62f2bca7f3870c1152153ef399", size = 376707, upload-time = "2026-10-22T13:58:58.292Z" },
{ url = "https://files.pythonhosted.org/packages/9f/d5/2c574da9faf130a59c4c685ba1378f2f470b57fb9fa5d3b0f8b4e90579a0/brotli-1.2.1-cp315-cp315-win_arm64.whl", hash = "sha256:91097d66ebb8ca8d23372081a33405a44e2239445614888e38929bd7f5879dae", size = 390278, upload-time = "2026-10-22T13:58:59.512Z" },
]
[[package]]
@@ -653,52 +672,65 @@ wheels = [
[[package]]
name = "cryptography"
version = "50.0.1"
version = "50.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" },
{ url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" },
{ url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" },
{ url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" },
{ url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" },
{ url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" },
{ url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" },
{ url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" },
{ url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" },
{ url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" },
{ url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" },
{ url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" },
{ url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" },
{ url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" },
{ url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" },
{ url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" },
{ url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" },
{ url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" },
{ url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" },
{ url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" },
{ url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" },
{ url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" },
{ url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" },
{ url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" },
{ url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" },
{ url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" },
{ url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" },
{ url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" },
{ url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" },
{ url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" },
{ url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" },
{ url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" },
{ url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" },
{ url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" },
{ url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" },
{ url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" },
{ url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" },
{ url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" },
{ url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" },
{ url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" },
{ url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" },
{ url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" },
{ url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" },
{ url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" },
{ url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" },
{ url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" },
{ url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" },
{ url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" },
{ url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" },
{ url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" },
{ url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" },
{ url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" },
{ url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" },
{ url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" },
{ url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" },
{ url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" },
{ url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" },
{ url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" },
{ url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" },
{ url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" },
{ url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" },
{ url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" },
{ url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" },
{ url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" },
{ url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" },
{ url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" },
{ url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" },
{ url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" },
{ url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" },
{ url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" },
{ url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" },
{ url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" },
{ url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" },
{ url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" },
{ url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" },
{ url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" },
{ url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" },
{ url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" },
{ url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" },
{ url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" },
{ url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" },
{ url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" },
{ url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" },
{ url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" },
]
[[package]]
@@ -781,15 +813,15 @@ wheels = [
[[package]]
name = "django-countries"
version = "9.0.0"
version = "9.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/1e8feb818164c3f23465c08cc3bc1cd7da7101506268a138a1b92ea40339/django_countries-9.0.0.tar.gz", hash = "sha256:a993416af08a8a4e6e866d56b71c7ce92351c81a2543da213cd7899917567a42", size = 614144, upload-time = "2026-06-10T00:39:28.841Z" }
sdist = { url = "https://files.pythonhosted.org/packages/de/7e/54c144d213ccdd4f3e74e044c4b6cb7a0c2ccf2e738886fd61b9a0a376df/django_countries-9.1.0.tar.gz", hash = "sha256:c1027417e1d1ebad5ac8fbc45a7578fc400c4fc895d6b865e001c16ba4be0939", size = 614115, upload-time = "2026-09-10T22:24:18Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/bb/5a17b339852f6486d02b4dd0033132082976c71f049ee909ccb42917be52/django_countries-9.0.0-py3-none-any.whl", hash = "sha256:21fce461733c856355c487d1f24b71338599482505004a0c2e521fd574a59fb1", size = 931759, upload-time = "2026-06-10T00:39:26.851Z" },
{ url = "https://files.pythonhosted.org/packages/9c/86/f4a3a1bc0827e19bd0ec7267d80725e4f02916d34a279747672436a0aa5b/django_countries-9.1.0-py3-none-any.whl", hash = "sha256:18c9f6e1a8e32356656b1db5ee94121310cbc3b91dc78171052a9ef9d57d5318", size = 928029, upload-time = "2026-09-10T22:24:16.325Z" },
]
[[package]]
@@ -806,38 +838,40 @@ wheels = [
[[package]]
name = "django-filter"
version = "26.1"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/3e/563965173d4cbb5fc308087e7b3d11a115b7b67273d093622480b1e31f78/django_filter-26.1.tar.gz", hash = "sha256:66ea04031b068c77c86e1ac26ced7a3f8f13ce797f5795751707e3deefc58054", size = 144299, upload-time = "2026-07-11T09:27:02.767Z" }
sdist = { url = "https://files.pythonhosted.org/packages/07/ee/c0f950fe24f3e0b69d054ad957f9229632b50bdc78e968f966d8b4efe16a/django_filter-26.2.tar.gz", hash = "sha256:fd5cc83995fbe9f5f07fb5dcda16fde0f04de1ecf8ef82628b6c0ec921b751af", size = 144828, upload-time = "2026-10-03T12:01:01.007Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/01/afffed1e3c4540fb75bf550a18b6176a9f6371b5f3e52b69a28995b6480c/django_filter-26.1-py3-none-any.whl", hash = "sha256:7d98ef2899218e6242619b532cb1b95af14e09dfcf74844aecb550ad27b59ff2", size = 94069, upload-time = "2026-07-11T09:27:01.012Z" },
{ url = "https://files.pythonhosted.org/packages/4a/92/6b098caa5b980a9d13a8db4825b7670410fa572e9195e39648f7700f2a37/django_filter-26.2-py3-none-any.whl", hash = "sha256:df8f737841d6359df00b84dda9b5ab59067fe60292091f1bdae1e3e6281cedb0", size = 94077, upload-time = "2026-10-03T12:00:59.162Z" },
]
[[package]]
name = "django-lasuite"
version = "0.0.29"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "djangorestframework" },
{ name = "joserfc" },
{ name = "mozilla-django-oidc" },
{ name = "pyjwt" },
{ name = "requests" },
{ name = "requests-toolbelt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/ab/72a79b7133fb2d17835bef40f45f7e6d2b4343469c37fd00386122e9068d/django_lasuite-0.0.29.tar.gz", hash = "sha256:831cb859e45e33963c9d89168c2de14f9844e805000b0f0d1512ffd0b9731a39", size = 35248, upload-time = "2026-08-25T13:03:43.597Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0f/cc/35335b826b039c43da68a0e624e154f60c3ac6c83f01f9b4ad6ce91faf03/django_lasuite-1.0.0.tar.gz", hash = "sha256:8921aaba5a78f1f9728cee2cc4596755478108f17146d4a2fccb393c043a05a3", size = 45815, upload-time = "2026-10-30T14:57:31.764Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/cf/86648643221816f18ebd5a3e59b6d2a85ee85b38b19c7abe939f798a2845/django_lasuite-0.0.29-py3-none-any.whl", hash = "sha256:33bcff90ced2709190ef01241badb17ceed08a32725b9cdd3105650b36da303b", size = 54899, upload-time = "2026-08-25T13:03:42.212Z" },
{ url = "https://files.pythonhosted.org/packages/a4/6f/d1df3f2881cda711e569b8e685ad3a846ca9c71226340597cee1c09beae0/django_lasuite-1.0.0-py3-none-any.whl", hash = "sha256:23d589be9496b1f484772b49f3c4c4a81e70affd537e3c50abf15303c84d7cb0", size = 68529, upload-time = "2026-10-30T14:57:30.416Z" },
]
[package.optional-dependencies]
all = [
{ name = "celery" },
{ name = "cryptography" },
{ name = "django-configurations" },
{ name = "djangorestframework" },
{ name = "joserfc" },
{ name = "mozilla-django-oidc" },
{ name = "pyjwt" },
{ name = "python-icap" },
{ name = "requests" },
{ name = "requests-toolbelt" },
]
[[package]]
@@ -854,16 +888,16 @@ wheels = [
[[package]]
name = "django-pydantic-field"
version = "0.5.4"
version = "0.5.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "pydantic" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/3d/d2390f1383eb60b25c7b68b3493af566831b0db540e47ed85a395727bae2/django_pydantic_field-0.5.4.tar.gz", hash = "sha256:3c884299ea777e8221e4c0ff222e078960ba3576a3b6970636629524e5d6cf39", size = 22013, upload-time = "2026-02-22T13:47:30.633Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/3a/ffc778bfd04071465780fe065d27be2277cf4009f60143be071f0f22ae93/django_pydantic_field-0.5.6.tar.gz", hash = "sha256:222dc7ee7ffe5fed311b3121811e02cbf26b72c763273e83c4241b72f3d8d3e6", size = 25404, upload-time = "2026-10-22T00:55:33.231Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/88/1acbef013f172f9f03836d3c03639ae5604a46e9f1a921c6e623e60f8594/django_pydantic_field-0.5.4-py3-none-any.whl", hash = "sha256:5c080b5f0ea17ae11b5305d649778a87437c61a403894933337fe3e295f627a9", size = 36963, upload-time = "2026-02-22T13:47:29.613Z" },
{ url = "https://files.pythonhosted.org/packages/75/e0/477662aa27197a0cedf07894e4e77578fd2e4bbda703a8308b6091df8cfb/django_pydantic_field-0.5.6-py3-none-any.whl", hash = "sha256:7bcdaac51ac163698b6170a65880086e74b461403c66ad5a8d4e64b0e295b08b", size = 37053, upload-time = "2026-10-22T00:55:32.193Z" },
]
[[package]]
@@ -910,14 +944,14 @@ wheels = [
[[package]]
name = "djangorestframework"
version = "3.18.0"
version = "3.18.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/bc/de04e3d4dc65e8b926700956ee70d4f084f2005603d21122d4d0683006fd/djangorestframework-3.18.0.tar.gz", hash = "sha256:2323a5111837e0b784dcb8323abc78ecc54fa2a5af7aff2677cf50cdd849477f", size = 913667, upload-time = "2026-08-07T14:53:33.571Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8d/b2/1cee76b188301f7d03169ca77fab2b3d3ec5d01b3469189f944af2d7f11a/djangorestframework-3.18.3.tar.gz", hash = "sha256:446a9b352e7eff630421ab3f2328bd2401b109a9470afa4a31189994911ed030", size = 923563, upload-time = "2026-10-06T21:33:51.521Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/d7/7461738e819b853275b35fd35712a85806c09a327e1fd5f8ff766bdd317e/djangorestframework-3.18.0-py3-none-any.whl", hash = "sha256:381fc44d3249c9565c5f723850855b734e99030eb30957a49f506d3fe11d7dcb", size = 900032, upload-time = "2026-08-07T14:53:31.881Z" },
{ url = "https://files.pythonhosted.org/packages/c4/dd/de5f4543765bec94e036a5e9593a42f8fa7efec8de78eb56b9ca834385f3/djangorestframework-3.18.3-py3-none-any.whl", hash = "sha256:8544bb674846731b1e3c9b309236ee1dc412905a0aa725be2ec193ca950a7d12", size = 903986, upload-time = "2026-10-06T21:33:49.917Z" },
]
[[package]]
@@ -948,14 +982,14 @@ wheels = [
[[package]]
name = "drf-spectacular-sidecar"
version = "2026.8.1"
version = "2026.10.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/82/eb18e2108b8ceaa50a01d2cdb35ab5ce198c18f062448b2d29d6e89aabeb/drf_spectacular_sidecar-2026.8.1.tar.gz", hash = "sha256:9d9ee678e5ec70cdda2c7ed95cafcdb6141664dc3e11def8a8599c8ad36cf6f5", size = 2603635, upload-time = "2026-08-01T12:09:03.088Z" }
sdist = { url = "https://files.pythonhosted.org/packages/23/7f/e95c51746e66cae4b6ca35912ad2a211dee979647e9fce4f3b5aca780630/drf_spectacular_sidecar-2026.10.1.tar.gz", hash = "sha256:ec8d20b2594e8a10ebf22e5bab62ec313c7cf8793753a050290b209e16e81c9c", size = 2633804, upload-time = "2026-10-01T17:20:35.255Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/5b/5ea443b8c24b4a4815a6467a158c4511dfe8bed83f8861a27bdb29c4fc47/drf_spectacular_sidecar-2026.8.1-py3-none-any.whl", hash = "sha256:6b8784d3e8c6bd9aa740b14e874640697ab1bc67f5232d74fdd9ef84eaea6bc3", size = 2626283, upload-time = "2026-08-01T12:09:01.705Z" },
{ url = "https://files.pythonhosted.org/packages/55/09/13509939a12e953759fa2f281b28dc14813c8c331addf3838fccdc71b364/drf_spectacular_sidecar-2026.10.1-py3-none-any.whl", hash = "sha256:fe1d005d8ac9b5d0472f2fe5deeb2e7d6dcffaa24118044f22578b4b8c4b31f2", size = 2656316, upload-time = "2026-10-01T17:20:33.813Z" },
]
[[package]]
@@ -1158,7 +1192,7 @@ wheels = [
[[package]]
name = "ipython"
version = "9.16.1"
version = "9.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1172,9 +1206,9 @@ dependencies = [
{ name = "stack-data" },
{ name = "traitlets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/96/b150fe7e25a5a29ae9ac1374e71488639605d39a1ea4abb74c9ce33af235/ipython-9.16.1.tar.gz", hash = "sha256:5a3d1f9a47ff216d6cf9cf863124f6a2c1a198d1354c546a4d24a370a283b64c", size = 4515302, upload-time = "2026-08-03T08:36:15.571Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/32/99451b1283ec5d92ad77073f12e1c667dc10775384d8f15c2914207149dd/ipython-9.17.1.tar.gz", hash = "sha256:8919be8c27f20a6f4423145028063f6637b42a03ce57665bb12015ee1f073529", size = 4539289, upload-time = "2026-09-01T08:29:32.6Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/8e/1239df488393d61076653bfb29f759d0f60cab8e030abdf7c17c31539b51/ipython-9.16.1-py3-none-any.whl", hash = "sha256:4acae635506f6d352d94c4899a19d5f85f8bc4d230932342dca556fdab1c69b4", size = 625974, upload-time = "2026-08-03T08:36:13.654Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1e/65b59cf518c106aa755e7f7da3099027738687a862ec785060702a481320/ipython-9.17.1-py3-none-any.whl", hash = "sha256:6d1645743cfd1a07eb695d85aa2b5fa66721f8cbae9431d4049f7084bbf06509", size = 639038, upload-time = "2026-09-01T08:29:30.673Z" },
]
[[package]]
@@ -1280,18 +1314,20 @@ redis = [
[[package]]
name = "livekit-api"
version = "1.2.0"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "idna" },
{ name = "livekit-protocol" },
{ name = "multidict" },
{ name = "protobuf" },
{ name = "pyjwt" },
{ name = "types-protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/19/36ff6712ec638a4b7dad4d8f03795952e401dc31db0b04cddec7892650da/livekit_api-1.2.0.tar.gz", hash = "sha256:a89817b3bca9584873786ff07209839308217537a42f95ecb2609aafaa109ddc", size = 20778, upload-time = "2026-07-11T23:20:54.781Z" }
sdist = { url = "https://files.pythonhosted.org/packages/18/0c/a6ced29213cf99f868b8456157a9778294336d2aa53e83cc790b4ed2a364/livekit_api-1.2.2.tar.gz", hash = "sha256:689753198dfefe58b007581fcce09e1089b2ad80adb3f9445798e55abdc9a264", size = 23116, upload-time = "2026-10-16T03:34:58.584Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/e7/8926f16d4bc1b2e0ae46d4a507321bb899396d263a757f1adaabcd3b3867/livekit_api-1.2.0-py3-none-any.whl", hash = "sha256:307f8e5cfb0358c3ca091814ab768af55896022151bcd7f951954ccefa036a24", size = 26499, upload-time = "2026-07-11T23:20:53.736Z" },
{ url = "https://files.pythonhosted.org/packages/50/39/d103c5314fb508832a5abc92b8b3394cb50f79cd07aac350bf5a6e7c9c3a/livekit_api-1.2.2-py3-none-any.whl", hash = "sha256:64f82ed90aaef2dc982dbc7f9dcda6f5dc958928ce77ef2b582459d8e2c87b6b", size = 28806, upload-time = "2026-10-16T03:34:57.178Z" },
]
[[package]]
@@ -1309,11 +1345,11 @@ wheels = [
[[package]]
name = "markdown"
version = "3.10.3"
version = "3.11.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/29/6f/da4c6aea59b3001f2e8c0ec7497475aadaf3b021c10cab5b2858f0f32b26/markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f", size = 372596, upload-time = "2026-07-30T19:05:29.005Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f4/32/0b4d0297064244f04af4331b65de16237cc3cce07474171ee14222a28981/markdown-3.11.2.tar.gz", hash = "sha256:87bf5d90f2d2882e2c02fcda182c2800456b985bff38913867c49706481514b0", size = 491445, upload-time = "2026-10-19T19:41:42.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/69/4a5af2bc115a9a33fefe51709749de8262be3f9ba063d1753a837cdbc49c/markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea", size = 110757, upload-time = "2026-07-30T19:05:27.883Z" },
{ url = "https://files.pythonhosted.org/packages/fe/66/bcab3b677b71ad286b4e607e3427499537c76e13ad2e22584ba7a7d9882c/markdown-3.11.2-py3-none-any.whl", hash = "sha256:f80390747a2a0ff2ba54da074ce4f862f72a6685f608cd2637b646a6d83993f5", size = 114266, upload-time = "2026-10-19T19:41:41.244Z" },
]
[[package]]
@@ -1339,7 +1375,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.36.1"
version = "1.37.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -1410,46 +1446,46 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "aiohttp", specifier = "==3.14.5" },
{ name = "boto3", specifier = "==1.43.80" },
{ name = "boto3", specifier = "==1.43.118" },
{ name = "brevo-python", specifier = "==1.2.0" },
{ name = "brotli", specifier = "==1.2.0" },
{ name = "brotli", specifier = "==1.2.1" },
{ name = "celery", extras = ["redis"], specifier = "==5.6.3" },
{ name = "cryptography", specifier = "==50.0.1" },
{ name = "cryptography", specifier = "==50.0.2" },
{ name = "dj-database-url", specifier = "==3.1.2" },
{ name = "django", specifier = "==5.2.17" },
{ name = "django-configurations", specifier = "==2.5.1" },
{ name = "django-cors-headers", specifier = "==4.9.0" },
{ name = "django-countries", specifier = "==9.0.0" },
{ name = "django-filter", specifier = "==26.1" },
{ name = "django-lasuite", extras = ["all"], specifier = "==0.0.29" },
{ name = "django-countries", specifier = "==9.1.0" },
{ name = "django-filter", specifier = "==26.2" },
{ name = "django-lasuite", extras = ["all"], specifier = "==1.0.0" },
{ name = "django-parler", specifier = "==2.4" },
{ name = "django-pydantic-field", specifier = "==0.5.4" },
{ name = "django-pydantic-field", specifier = "==0.5.6" },
{ name = "django-redis", specifier = "==7.0.0" },
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
{ name = "django-timezone-field", specifier = ">=5.1" },
{ name = "djangorestframework", specifier = "==3.18.0" },
{ name = "djangorestframework", specifier = "==3.18.3" },
{ name = "dockerflow", specifier = "==2026.3.4" },
{ name = "drf-spectacular", specifier = "==0.30.0" },
{ name = "easy-thumbnails", specifier = "==2.10.1" },
{ name = "factory-boy", specifier = "==3.3.3" },
{ name = "gunicorn", specifier = "==26.2.0" },
{ name = "jsonschema", specifier = "==4.26.0" },
{ name = "livekit-api", specifier = "==1.2.0" },
{ name = "markdown", specifier = "==3.10.3" },
{ name = "livekit-api", specifier = "==1.2.2" },
{ name = "markdown", specifier = "==3.11.2" },
{ name = "menshen-client", specifier = "==0.2.0" },
{ name = "mozilla-django-oidc", specifier = "==5.0.2" },
{ name = "nested-multipart-parser", specifier = "==1.6.0" },
{ name = "phonenumbers", specifier = "==9.0.37" },
{ name = "phonenumbers", specifier = "==9.0.41" },
{ name = "pillow", specifier = "==13.0.0" },
{ name = "posthog", specifier = "==7.44.0" },
{ name = "psycopg", extras = ["binary", "pool"], specifier = "==3.3.4" },
{ name = "pydantic", specifier = "==2.13.4" },
{ name = "pyjwt", specifier = "==2.14.0" },
{ name = "posthog", specifier = "==7.75.11" },
{ name = "psycopg", extras = ["binary", "pool"], specifier = "==3.3.6" },
{ name = "pydantic", specifier = "==2.14.1" },
{ name = "pyjwt", specifier = "==2.15.1" },
{ name = "python-frontmatter", specifier = "==1.3.0" },
{ name = "python-magic", specifier = "==0.4.27" },
{ name = "redis", specifier = "==5.2.1" },
{ name = "requests", specifier = "==2.34.2" },
{ name = "sentry-sdk", specifier = "==2.68.1" },
{ name = "sentry-sdk", specifier = "==2.73.0" },
{ name = "urllib3", specifier = "==2.8.0" },
{ name = "whitenoise", specifier = "==6.12.0" },
]
@@ -1457,10 +1493,10 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "django-extensions", specifier = "==4.1" },
{ name = "drf-spectacular-sidecar", specifier = "==2026.8.1" },
{ name = "drf-spectacular-sidecar", specifier = "==2026.10.1" },
{ name = "freezegun", specifier = "==1.5.5" },
{ name = "ipdb", specifier = "==0.13.13" },
{ name = "ipython", specifier = "==9.16.1" },
{ name = "ipython", specifier = "==9.17.1" },
{ name = "pyfakefs", specifier = "==6.2.0" },
{ name = "pylint", specifier = "==4.1.3" },
{ name = "pylint-django", specifier = "==2.8.0" },
@@ -1469,9 +1505,9 @@ dev = [
{ name = "pytest-django", specifier = "==4.14.0" },
{ name = "pytest-icdiff", specifier = "==0.9" },
{ name = "pytest-xdist", specifier = "==3.8.0" },
{ name = "responses", specifier = "==0.26.2" },
{ name = "ruff", specifier = "==0.16.4" },
{ name = "types-requests", specifier = "==2.33.0.20260712" },
{ name = "responses", specifier = "==0.26.3" },
{ name = "ruff", specifier = "==0.17.2" },
{ name = "types-requests", specifier = "==2.33.0.20261006" },
]
[[package]]
@@ -1657,11 +1693,11 @@ wheels = [
[[package]]
name = "phonenumbers"
version = "9.0.37"
version = "9.0.41"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/36/ee5b6d7ccc28cb7848e8dfefce94df9178b6318ed56adab97550b6ba99ec/phonenumbers-9.0.37.tar.gz", hash = "sha256:8923ab413c575b27da2815c9bef48290181aab565398b5e7e23cc7ace212a293", size = 2312364, upload-time = "2026-08-14T05:29:13.533Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/df/cc0d70f1c79e436ea00d935b6352053d526252b81ce6c130d39eee846fb2/phonenumbers-9.0.41.tar.gz", hash = "sha256:dfa6f74eeac67c044b75313fe0af10774d7d1e1242241437279d4c2fb8027c01", size = 2311017, upload-time = "2026-10-08T10:51:09.778Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/68/544cb901964e8bfc3c8d215d5620e63a009b32437efaec905fdefdf59e26/phonenumbers-9.0.37-py2.py3-none-any.whl", hash = "sha256:55ae8243c1e2fe8ae2548d979e57155d9ae0f6084f8a1d6884050b143aff0a7c", size = 2595712, upload-time = "2026-08-14T05:29:10.464Z" },
{ url = "https://files.pythonhosted.org/packages/60/1a/4059026e9c8a4c3faeab4a45f5aa67fb77d1f0d9c767085ded69c5c533e2/phonenumbers-9.0.41-py2.py3-none-any.whl", hash = "sha256:ccf2ea44f8aa35c487f26146a31520ecedf8e1af1f57c803678ecb5ef5c01668", size = 2594658, upload-time = "2026-10-08T10:51:07.317Z" },
]
[[package]]
@@ -1746,7 +1782,7 @@ wheels = [
[[package]]
name = "posthog"
version = "7.44.0"
version = "7.75.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backoff" },
@@ -1754,9 +1790,9 @@ dependencies = [
{ name = "requests" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3b/20/485d1ba20f9eec3d9de51232b5388cead56faf80321125f3ac4cb822666c/posthog-7.44.0.tar.gz", hash = "sha256:d413a522b6363660d00e56e7e8e9e31722ca2ecb66dd7a0709b02671fec14a34", size = 479497, upload-time = "2026-08-25T16:31:32.831Z" }
sdist = { url = "https://files.pythonhosted.org/packages/20/ee/6c0ad2557d82a626dbd14453fada5b93c4c78a9e696f4e74b5672facd29a/posthog-7.75.11.tar.gz", hash = "sha256:c55e8de3943cc9cd86a8eda893f5ea1b9d631f52a2758dd14c0c17ef52632908", size = 819642, upload-time = "2026-10-23T15:26:56.068Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/93/ec/937930cb7866fcab2d0b05f16e878ea525a94abcbafffdc651f81cd9f6c6/posthog-7.44.0-py3-none-any.whl", hash = "sha256:744e943d085a7a15ca9458bef69896b831265b3d4b89394a8b068fb580d501ca", size = 567172, upload-time = "2026-08-25T16:31:31.357Z" },
{ url = "https://files.pythonhosted.org/packages/50/8c/7765ea5122fe96daf9d3db4653fc37f2ca5115e2f6f7f4d74f0867433a8a/posthog-7.75.11-py3-none-any.whl", hash = "sha256:c65d173b24ec82cbf0f977f4779348512a7f71b761809786e87ae47e347ebab1", size = 958056, upload-time = "2026-10-23T15:26:54.116Z" },
]
[[package]]
@@ -1919,14 +1955,14 @@ wheels = [
[[package]]
name = "psycopg"
version = "3.3.4"
version = "3.3.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
sdist = { url = "https://files.pythonhosted.org/packages/76/26/3ea4ca5eaea1c0debcdf7ee7c1613fbe721dc27a03c461c0817ffd8a0601/psycopg-3.3.6.tar.gz", hash = "sha256:c081f2250df751a943036e42db6df4571c66cd0aabe8291a7a506512b12007d2", size = 168171, upload-time = "2026-09-18T13:22:55.152Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
{ url = "https://files.pythonhosted.org/packages/4e/de/748bd7609c71cae5d737f0ba9192f19329f70180ecda8fff3cac02c5abe3/psycopg-3.3.6-py3-none-any.whl", hash = "sha256:a1db9f7148b06a28606767efaca51fa6f9398c5c0a3810519be69d7000bdb631", size = 215490, upload-time = "2026-09-18T13:15:29.374Z" },
]
[package.optional-dependencies]
@@ -1939,31 +1975,42 @@ pool = [
[[package]]
name = "psycopg-binary"
version = "3.3.4"
version = "3.3.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" },
{ url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" },
{ url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" },
{ url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" },
{ url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" },
{ url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" },
{ url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" },
{ url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" },
{ url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" },
{ url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" },
{ url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" },
{ url = "https://files.pythonhosted.org/packages/48/a6/828c9185701dab71b234c2a76c38a08b098ebfec5020716b4e93807492b5/psycopg_binary-3.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:28b7398fdd19db3232c884fb24550bdfe951221f510e195e233299e4c9b78f97", size = 4607292, upload-time = "2026-05-01T23:30:38.962Z" },
{ url = "https://files.pythonhosted.org/packages/92/58/5b40dbc9d839045c9dae956960e4fb6d20bcabe6c59a2aa34fc3a371913f/psycopg_binary-3.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fbaa292a3c8bb61b45df1ad3da1908ccee7cb889db9425e3557d9e34e2a4829", size = 4687023, upload-time = "2026-05-01T23:30:47.227Z" },
{ url = "https://files.pythonhosted.org/packages/85/a9/793f0ac107a9003b48441d0d1f9f616d96e0f37458dd8dc12528ceff55fb/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94596f9e7633ee3f6440711d43bb70aa31cc0a46a900ab8b4201a366ace5c9e7", size = 5486985, upload-time = "2026-05-01T23:30:55.517Z" },
{ url = "https://files.pythonhosted.org/packages/8f/26/42e8533497e2592334f68ec529cf5f840f7fa4e99575a4bb61aa184dbfbf/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c0056529e68dbe9184cd4019a1f3d8f3a4ead2f6fc7a5afcf27d3314edd1277", size = 5168745, upload-time = "2026-05-01T23:31:01.904Z" },
{ url = "https://files.pythonhosted.org/packages/15/af/b7151776cc08d5935d45c833ec818a9beb417cf7c08239af1aafbdae78ee/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c09aad7051326e7603c14e50636db9c01f78272dc54b3accff03d46370461e6", size = 6761486, upload-time = "2026-05-01T23:31:14.511Z" },
{ url = "https://files.pythonhosted.org/packages/d0/ed/c92533b9124712d592cbf1cd6c76da933a2e0acea81dfe1fbe7e735f0cff/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:514404ed543efd620c85602b747df2a23cf1241b4067199e1a66f2d2757aaa41", size = 4997427, upload-time = "2026-05-01T23:31:20.901Z" },
{ url = "https://files.pythonhosted.org/packages/a2/23/ccadfd0de416aa188356daa199453af24087b042e296088706d190ae0295/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:46893c26858be12cc49ca4226ed6a60b4bfccadd946b3bebb783a60b38788228", size = 4533549, upload-time = "2026-05-01T23:31:26.204Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a0/c8f43cee36386f7bc891ab41a9d31ea07cf9826038e732da79f26b1e5f34/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:df1d567fc430f6df15c9fcf67d87685fc49bdb325adc0db5af1adfb2f44eb5c9", size = 4210256, upload-time = "2026-05-01T23:31:33.884Z" },
{ url = "https://files.pythonhosted.org/packages/4e/2c/c1547871be3790676e8868b38655496422f94f0978dfb66b74bdba2f1676/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6b9016b1714da4dd5ecaaa75b82098aa5a0b87854ce9b092e21c27c4ae23e014", size = 3946204, upload-time = "2026-05-01T23:31:39.626Z" },
{ url = "https://files.pythonhosted.org/packages/c4/b1/f6670f00fa7ea601584623f6c11602ab92117d83eaff885e0210f6de7418/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:47c656a8a7ba6eb0cff1801a4caaa9c8bdc12d03080e273aff1c8ac39971a77e", size = 4255811, upload-time = "2026-05-01T23:31:44.986Z" },
{ url = "https://files.pythonhosted.org/packages/eb/e6/5fff07a70d1f945ed90ae131c3bd76cab32beff7c58c6db15ad5820b6d1f/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8", size = 3666849, upload-time = "2026-05-01T23:31:51.165Z" },
{ url = "https://files.pythonhosted.org/packages/b4/c3/c072584b69ad44a747b448cfc9766fecb8aae56e372a017e2ef668790057/psycopg_binary-3.3.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ad8f35e67cc16d1fad1fa8c88972dc9b3a3141ea67897399904edab96a301b6", size = 4712284, upload-time = "2026-09-18T13:19:13.451Z" },
{ url = "https://files.pythonhosted.org/packages/0a/b9/4283b785339e8e2318d03048994b093d650ea6289fabaa806b765dc0d449/psycopg_binary-3.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373704aea331d3f3e3402c125a1543f5875e2986ebb54f97d1647942161f803f", size = 4772031, upload-time = "2026-09-18T13:19:18.524Z" },
{ url = "https://files.pythonhosted.org/packages/6f/72/7a1321d359246769fff1affffbd0132785a28f7f63c18524c15a502398f4/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b82491019b884d62318b5f30706c3d7e6d4e5a6cb7eabcb3edc0c1b0fdaceae9", size = 5556392, upload-time = "2026-09-18T13:19:24.418Z" },
{ url = "https://files.pythonhosted.org/packages/de/b0/c6f8a0585a5dacbea74e130bcfc66629390e8f5bbc79d2a8e806e8952150/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cec5ea900390897d0b46130f60bc2883bf19c314f9044235217c8be88b0ef269", size = 5237855, upload-time = "2026-09-18T13:19:31.257Z" },
{ url = "https://files.pythonhosted.org/packages/e2/fc/c3a7a8bbef7e945ec584ac61d460a612363ea398511cd0e220242b1d69f1/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98c02090d88f2ebc0ec1e8da538f77d225ce0fffecf372aa39262e62a1b054ef", size = 6833856, upload-time = "2026-09-18T13:19:43.622Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f2/8e80b921db728ebb68fc105bd7c4277f908210ad755bd6481d5ea7add740/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ee2c4728c691245e24501fcd7a97b5b381236b9985bc445bba88cdce7d1b5784", size = 5070730, upload-time = "2026-09-18T13:19:49.968Z" },
{ url = "https://files.pythonhosted.org/packages/54/6a/5b313e0c5348244f0e973aff3258bf86766656256d5ece8d541a53e35b4a/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f19cc87343eaa55255e76b31259a570072ac95d6ae82c92dd34b97691f5e49dc", size = 4598089, upload-time = "2026-09-18T13:19:56.426Z" },
{ url = "https://files.pythonhosted.org/packages/32/e9/db7f76ec24bf6699e92bf604e5c4bae10664a681a8999ef42aa0faf0f2c6/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdccb3a0e184b03e9baa673b15a809cf36c339c85dbda0ebc25a698846dfbee8", size = 4278481, upload-time = "2026-09-18T13:20:04.681Z" },
{ url = "https://files.pythonhosted.org/packages/61/83/72c67013656f4d6b547caabffb193e91d57e63f90eefdcc6d045c400e97d/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9892188bb15e5803beb51afe8a25add6b56be391a53058e8bca03b74e1e6bf22", size = 4009229, upload-time = "2026-09-18T13:20:11.905Z" },
{ url = "https://files.pythonhosted.org/packages/82/35/5e4500df2c999eb0faed8b184e6958b834172128274f06167a5deef4c19c/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3af90f92769d8cc10f94515ee7a0aef36ea85ca733a0ce22858f6e0953f41138", size = 4321467, upload-time = "2026-09-18T13:20:17.949Z" },
{ url = "https://files.pythonhosted.org/packages/55/7f/e350e1cf498ba2565c3f87b12f429d2012eb86b76c2b3845a19ee5fbb4d6/psycopg_binary-3.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:0ebfad5d131de9f892ae9e70cc7616207768b6714b66a52d4612b8ceaf78b372", size = 3658179, upload-time = "2026-09-18T13:20:22.691Z" },
{ url = "https://files.pythonhosted.org/packages/6d/b9/60711317c284a442511644ea7185b56ebe627606d6741e732cd16108c47b/psycopg_binary-3.3.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b3f75dee0f9afafabe4edc52c4842f1e1878ed2069bd05b22d6fe961e97e4dba", size = 4720512, upload-time = "2026-09-18T13:20:29.278Z" },
{ url = "https://files.pythonhosted.org/packages/63/da/28befc84454cbc6374550de7746f591f8fe1b6165c1fce249652cc8291c4/psycopg_binary-3.3.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5927b7ba63153cd8e9862987290a2b783a5c590daf2a4ef981700cc3569166d4", size = 4782318, upload-time = "2026-09-18T13:20:35.401Z" },
{ url = "https://files.pythonhosted.org/packages/a4/8a/0d21c2c833cdc0d4244c77e858e0ed37fa2abec2623be4fd686f617109ce/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0bf08b749cc144f33b44a91b78e3f71c60eb07963746a0df5a100b36ce3d7475", size = 5567460, upload-time = "2026-09-18T13:20:41.902Z" },
{ url = "https://files.pythonhosted.org/packages/49/6d/7692d0d4e656b6cc9868d8acc2e3b42f17a0db4a625400a6d093cb0533a1/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31cd942c23f613276b81a6e6598cefa12960058b0f46e1e874b540c793f6aca5", size = 5246902, upload-time = "2026-09-18T13:20:47.661Z" },
{ url = "https://files.pythonhosted.org/packages/d4/c1/b8a1f18fb1b7558a17f57f7cb3fc8bc93189feea2958925950b3acb15743/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4690cf67738f0e0e49a32aeec99bf0e4595cc2b4f1af984a4345394b1dcff91a", size = 6847192, upload-time = "2026-09-18T13:20:56.874Z" },
{ url = "https://files.pythonhosted.org/packages/a5/76/404f33519167c65cca88ec4998776f1dbebccc301ee977f0e62c47fb0826/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ad1c785e784cfd87e8436c6b7702f2d321fc39601bbaf29bc63a41a867091638", size = 5079573, upload-time = "2026-09-18T13:21:04.155Z" },
{ url = "https://files.pythonhosted.org/packages/f0/d9/79e8fbc8f37262a415f3550f0bcc5f98037442bf3d12ef6cbae2056655ae/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:79a2a1c3449f6c3409427078ed1cec10de79f3023cb5f2504f0597d350ad46c7", size = 4613633, upload-time = "2026-09-18T13:21:10.664Z" },
{ url = "https://files.pythonhosted.org/packages/d4/47/96225db74be7d2ce04b3a58678b53cda610225055edf5faa775c9f501d8b/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:86147cb5d140341c3363fb5bacce31f8d5543902a46699d3c536b101bbceaf9e", size = 4293375, upload-time = "2026-09-18T13:21:16.027Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d2/18e9c779a5efd565250329adaf529ecc2b8b2ed5be5cb0f6ccee208cbfd9/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7308c93cf0b19bbaf8e6ff0a6ad50d3c442385739245fe15a8d593bf841734a6", size = 4019883, upload-time = "2026-09-18T13:21:21.587Z" },
{ url = "https://files.pythonhosted.org/packages/ef/28/0cc654afc6c2cda982767f5679d3646b30b1ec86545bdaa9402202d6776c/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05a83ac9fd52b9bca7cb5ab04b3691163170bd16f53defa27216ea3aa07ee781", size = 4332607, upload-time = "2026-09-18T13:21:27.63Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3e/0a753a74fbd7aef120f286c016e09d3cc3f1daf7688f4a145d27281260b2/psycopg_binary-3.3.6-cp314-cp314-win_amd64.whl", hash = "sha256:1fbd30e537dab22cafdf080608f10148fe2a5f3a61294ddb5113caac8a623840", size = 3755671, upload-time = "2026-09-18T13:21:33.855Z" },
{ url = "https://files.pythonhosted.org/packages/0e/b1/a372b9c02aea50148e71c9853e19efca8fa5ae2010a8e27243b9b8f790c0/psycopg_binary-3.3.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:bf8c8481d026b85dd70c5fa7dde85b2333aed0b32a2602bcd38a900cbd78a49c", size = 4719571, upload-time = "2026-09-18T13:21:41.437Z" },
{ url = "https://files.pythonhosted.org/packages/65/7c/811e3828c6b82e2f10c6c9cdd963cfc66f3e024026e5a69ac18530bad984/psycopg_binary-3.3.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:b599defe9190b17e9907c8b4d114c181e702c87efcd1b8a0ad40971cdcc4634a", size = 4781230, upload-time = "2026-09-18T13:21:49.516Z" },
{ url = "https://files.pythonhosted.org/packages/3e/15/9a784eed813ea9e97c294af3ead63d02b7b203502c66380336c50065e441/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b8ece331509f7a975b90501f41e83ad905e4141753fedf3f2711b2bc70a8efbc", size = 5566111, upload-time = "2026-09-18T13:21:58.089Z" },
{ url = "https://files.pythonhosted.org/packages/68/16/47194e002007c27337b11e49bf459c4b19727463f9aff2e1a90917bcc806/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c61617eaae0112ca154da87ffb99b73af2c74067acac28dfb9a4455b019dff2e", size = 5249963, upload-time = "2026-09-18T13:22:06.695Z" },
{ url = "https://files.pythonhosted.org/packages/53/84/5dcf9f310b11f0675cd860c6b2c70f58ce61798a3ee3f6f962b53fa358ca/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6d19cb4999d03231e8730a5f66c8f5068bc3b532677eb39dab0f600bff3e312", size = 6847925, upload-time = "2026-09-18T13:22:13.088Z" },
{ url = "https://files.pythonhosted.org/packages/f3/06/1957a06dc22963c418c27b284929579de84f29c37ad1abe6dc6ee9e8cf25/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8cbb54454dbf1bbf2ff08dd7693e8d94ac94b1a20f70f4b3b813d52ecb5cbc1", size = 5087720, upload-time = "2026-09-18T13:22:17.959Z" },
{ url = "https://files.pythonhosted.org/packages/21/43/ac07d042bae99b57bf123bb473632f29af544008094da0ffd285ab8011e2/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dc75da5a20951049f7b773145f998f69d181adad9c58a0ff36e0cf1d73c10e10", size = 4613412, upload-time = "2026-09-18T13:22:26.719Z" },
{ url = "https://files.pythonhosted.org/packages/aa/b1/019156fbeafcefb4cccc9d109de4699493bceb8313c7545c8349e089dfbc/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:955e3dd94da361e052d2e49acf591017158dc8f8ed2c8a42c2e3943403c39dc2", size = 4292618, upload-time = "2026-09-18T13:22:33.042Z" },
{ url = "https://files.pythonhosted.org/packages/5d/0f/62113dc6b1df65983a1f2fc816c04b1edfa22f2ae9d4abee74ed267f4a96/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:c7753871eb57e6a5f4646f6168590c6653073dea5e9e720b201c8875332df4c8", size = 4027121, upload-time = "2026-09-18T13:22:38.334Z" },
{ url = "https://files.pythonhosted.org/packages/5d/d5/cf0cbd1ea5a7d8167fe2c6953efde19101f7b193bd61a23e6d622ad6854c/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:303732e798fe6729f8e12021b9c96107df8e95ecec4dd487c67b98ec2a59435e", size = 4336388, upload-time = "2026-09-18T13:22:45.576Z" },
{ url = "https://files.pythonhosted.org/packages/98/33/e2a5b36edf8aa422f6fa4b894756eb33dc93b36df5f65121280bb8b929c4/psycopg_binary-3.3.6-cp315-cp315-win_amd64.whl", hash = "sha256:2f122603f36050937982abf9668d8bc4769a79f7c93a65013b1c49f1cab7b56b", size = 3756154, upload-time = "2026-09-18T13:22:51.283Z" },
]
[[package]]
@@ -2007,7 +2054,7 @@ wheels = [
[[package]]
name = "pydantic"
version = "2.13.4"
version = "2.14.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -2015,65 +2062,96 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
{ url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" },
]
[[package]]
name = "pydantic-core"
version = "2.46.4"
version = "2.50.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
{ url = "https://files.pythonhosted.org/packages/ae/57/0e237d7091d2cd44a35d243b7344227f90440166860469cd036afc23a04d/pydantic_core-2.50.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d5e062c01286d861fd6a1c4ff6e063547b3e713067f2df033c0ff97ac2ca006b", size = 2222774, upload-time = "2026-10-11T18:32:43.103Z" },
{ url = "https://files.pythonhosted.org/packages/f7/b9/c720e56858d4e1539503297ed37063e0c08e0f3541c41b777f6a800f75fa/pydantic_core-2.50.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c003c3b7f49debb893d2d85ae099ac5959c9839e2f330fadb1fcdf7a6594482", size = 2024738, upload-time = "2026-10-11T18:32:44.587Z" },
{ url = "https://files.pythonhosted.org/packages/4d/b8/fbfc25875219cc060e613170ff10e850c6d8924beb4910da57c2ee3ba1d2/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409e0ea40ec30d9158f33574fd758e689f6045a0f2596701828c27816ca9687d", size = 2047049, upload-time = "2026-10-11T18:32:46.164Z" },
{ url = "https://files.pythonhosted.org/packages/d7/43/34210124d504c553688f2f04b48500b131237528ac545b445e0d6d30e0d5/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:131059670f1d2444269b8585cb888963994871932447c08b39ac6a51fcfef658", size = 2173415, upload-time = "2026-10-11T18:32:47.722Z" },
{ url = "https://files.pythonhosted.org/packages/65/cf/6e178e8fdc11da5965bef980983bf46326a42f436871dd51ba0a57f39df1/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dbcbee53bf17196a7f745aa9bf5a9603953a1e365b1f020be3207c676a3e7c4", size = 2361719, upload-time = "2026-10-11T18:32:49.217Z" },
{ url = "https://files.pythonhosted.org/packages/11/14/bd5169d356aa91bf777e28ba0b281c192d286d03c2812c9c9db023a2f00e/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:325c23f3e35cfbf0fe3486fa5f7260d1e45885173002d30a28ca019994124255", size = 2439803, upload-time = "2026-10-11T18:32:51.025Z" },
{ url = "https://files.pythonhosted.org/packages/1c/bc/d79d000e5203ebef39af839f2ce77a777fcad6e08ad26af9a2fcd114ffc8/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e722e156d0444ecaefbe640bdb60928752bf2013e2b7a11cdb099aaae19bec", size = 2158402, upload-time = "2026-10-11T18:32:52.714Z" },
{ url = "https://files.pythonhosted.org/packages/1b/a5/4902cb5fd599422c130bd3124ab31ee8b771199bd56662702eed07d3fec7/pydantic_core-2.50.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:aa8224f10880d9bf1b5993988ba153d42a8b4f3f4f511f93b1f09c93ff613c72", size = 2229755, upload-time = "2026-10-11T18:32:54.126Z" },
{ url = "https://files.pythonhosted.org/packages/1a/f5/c1481f8669f6060d89110c9b1374173fc8767ca276b84f4870bd8acd5e3f/pydantic_core-2.50.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41bc8237121bd8dc8d888dfd6279fc166ffc88c1f1bf3a8bf00869680533ca4c", size = 2242303, upload-time = "2026-10-11T18:32:55.641Z" },
{ url = "https://files.pythonhosted.org/packages/04/f9/77fc3c7653ba9b6e42049e17e96b25388187d4a7c274ab1cb07f58ac8419/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:45c6266d071c241f2a168d45bf8c54344f0effce35e7e6b73afdec11f3687568", size = 2278345, upload-time = "2026-10-11T18:32:57.38Z" },
{ url = "https://files.pythonhosted.org/packages/95/9b/0579c5d12e7f2b16b27e6782427987341fcce07b0725e02ddb0b74add0c4/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:1deeacb112d14d3f4fcb16b165f7dbaf76c70ba6e82f37ba042bdab51970a0b8", size = 2449947, upload-time = "2026-10-11T18:32:58.896Z" },
{ url = "https://files.pythonhosted.org/packages/a8/ac/1b677db91eba54cc5922f4de6edc46ba45c2bd712dfdc0130382d158e214/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c96fd793b73d1b92e65570132505498fe7b21eaef73cdf74e67e5dfba7ac9e4", size = 2476889, upload-time = "2026-10-11T18:33:00.665Z" },
{ url = "https://files.pythonhosted.org/packages/4d/2a/3a9f6624ee3ea9ccba5249dde11418bb4c35780a7a92608f8768bd3fea39/pydantic_core-2.50.1-cp313-cp313-win32.whl", hash = "sha256:06ead20d39ffd6f2f6f2a8f8a6de67ff8bb1b4f14a8a30e058502514ee2ac685", size = 2101118, upload-time = "2026-10-11T18:33:02.329Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1f/323f78ddd9d9938aac420c1abb4e8ba799fc8bdab0acc67c0593b837c979/pydantic_core-2.50.1-cp313-cp313-win_amd64.whl", hash = "sha256:7816e98acc08119dc0f340ab167048ecc54126316330c1f0caf7c6756c88e28f", size = 2170840, upload-time = "2026-10-11T18:33:03.919Z" },
{ url = "https://files.pythonhosted.org/packages/cb/09/8497c52a739ae425c3ac2f7f56414cbc711c67d374346174f40fe2062644/pydantic_core-2.50.1-cp313-cp313-win_arm64.whl", hash = "sha256:c17799a62c142d61b8a3c51752a7cbc87fe2ad4ccfab10e628a77b405075c662", size = 2161633, upload-time = "2026-10-11T18:33:05.518Z" },
{ url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" },
{ url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" },
{ url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" },
{ url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" },
{ url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" },
{ url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" },
{ url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" },
{ url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" },
{ url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" },
{ url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" },
{ url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" },
{ url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" },
{ url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" },
{ url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" },
{ url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" },
{ url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" },
{ url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" },
{ url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" },
{ url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" },
{ url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" },
{ url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" },
{ url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" },
{ url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" },
{ url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" },
{ url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" },
{ url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" },
{ url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" },
{ url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" },
{ url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" },
{ url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" },
{ url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" },
{ url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" },
{ url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" },
{ url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" },
{ url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" },
{ url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" },
{ url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" },
{ url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" },
{ url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" },
{ url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" },
{ url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" },
{ url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" },
{ url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" },
{ url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" },
{ url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" },
{ url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" },
{ url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" },
{ url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" },
{ url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" },
{ url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" },
{ url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" },
{ url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" },
{ url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" },
{ url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" },
{ url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" },
{ url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" },
{ url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" },
]
[[package]]
@@ -2096,11 +2174,11 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.14.0"
version = "2.15.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" },
{ url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" },
]
[[package]]
@@ -2239,6 +2317,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/a3/17c284b4f4d8ad50f0f9ba70ad8fcc35c777aeafcdbbffdd91bbdc5ab379/python_frontmatter-1.3.0-py3-none-any.whl", hash = "sha256:9f7dd9260bec99044219159a329f64f039087f9d1a2124c9442556f2fe6f82ec", size = 10562, upload-time = "2026-05-20T19:21:43.323Z" },
]
[[package]]
name = "python-icap"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/0a/123c30f4041eabb94c587d35fb64aa9498435d028a8577340a4e5adc5224/python_icap-0.2.0.tar.gz", hash = "sha256:627869ac29119074542de6ae95370914854fdb35ee0a13c8ae4c55847cfcb80e", size = 684516, upload-time = "2026-07-08T22:21:58.842Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d8/f2/b437a5e08b94dfc4cd8505cfc3e63922247e9ce3957707550ab08c514560/python_icap-0.2.0-py3-none-any.whl", hash = "sha256:b648d55b68abe387849a124d1b4a443801342a566afd2141f4515953ed4bd738", size = 70680, upload-time = "2026-07-08T22:21:57.307Z" },
]
[[package]]
name = "python-magic"
version = "0.4.27"
@@ -2335,16 +2422,16 @@ wheels = [
[[package]]
name = "responses"
version = "0.26.2"
version = "0.26.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyyaml" },
{ name = "requests" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/1a/4af3e6d659394b809838490b144e4ab8d7ed3b9fecc7ca78f5d2f79b1a3d/responses-0.26.2.tar.gz", hash = "sha256:9c9259b46a8349197edebf43cfa68a87e1a2802ef503ff8b2fecbabc0b45afd8", size = 84030, upload-time = "2026-07-03T16:44:50.325Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9f/47/f216a33221db8eff328987661cf18371afee89c62a62b434b963d6b509c9/responses-0.26.3.tar.gz", hash = "sha256:b0c11ca8131b8b227b8d5108e6ed39772222bd5aab030ed430e8f99057c4c409", size = 86335, upload-time = "2026-08-26T19:17:24.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/28/693e1d9ebf72baa062ded80d837a035b86ce75eda5a269379e9e2b1008a8/responses-0.26.2-py3-none-any.whl", hash = "sha256:6fdfeabd58e5ec473b98dfe02e6d46d3173bd8dd573eff2ccccf1a05a5135364", size = 35609, upload-time = "2026-07-03T16:44:49.1Z" },
{ url = "https://files.pythonhosted.org/packages/6d/86/ca7958de70cb0752350575e98229368a3a2f746a2942034b3364e17312bb/responses-0.26.3-py3-none-any.whl", hash = "sha256:74474f799334ac4f37d93b6437ecc3bb1bb5c77a8d31780a338643be2dce0af8", size = 36289, upload-time = "2026-08-26T19:17:23.176Z" },
]
[[package]]
@@ -2430,27 +2517,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.16.4"
version = "0.17.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/58/690cf56da80c8f2c8bf12042b6017beac2c34fe961f65420bcb3fb7fac38/ruff-0.17.2.tar.gz", hash = "sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb", size = 5333400, upload-time = "2026-10-22T18:38:00.181Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" },
{ url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" },
{ url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" },
{ url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" },
{ url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" },
{ url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" },
{ url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" },
{ url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" },
{ url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" },
{ url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" },
{ url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" },
{ url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" },
{ url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" },
{ url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" },
{ url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" },
{ url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" },
{ url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
{ url = "https://files.pythonhosted.org/packages/98/b8/8b6d69c57e8fe1cc6af1aa7493b72a1a5b5a8afe34694e302025441729d4/ruff-0.17.2-py3-none-linux_armv6l.whl", hash = "sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e", size = 10183857, upload-time = "2026-10-22T18:37:14.956Z" },
{ url = "https://files.pythonhosted.org/packages/e8/61/12cc2ff7b2fa90a268ce1e2162c23aaf76e601d22623f6a31b0eeaa764b0/ruff-0.17.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d", size = 10210904, upload-time = "2026-10-22T18:37:17.985Z" },
{ url = "https://files.pythonhosted.org/packages/e8/7c/b8850a8e4ad29f64a827dd6c6902db4a9aa538fb2b9eef4e26084001a3bb/ruff-0.17.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7", size = 9408439, upload-time = "2026-10-22T18:37:20.964Z" },
{ url = "https://files.pythonhosted.org/packages/b7/b6/6a64fae86ca5e5e29d5adc311e5481de726cedf9aee607435a16c31c442a/ruff-0.17.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac", size = 10047078, upload-time = "2026-10-22T18:37:23.717Z" },
{ url = "https://files.pythonhosted.org/packages/10/19/23d376547715175aa1bd46df48df6aacc7c6439d9daf19cac15975180aa5/ruff-0.17.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160", size = 9990478, upload-time = "2026-10-22T18:37:26.584Z" },
{ url = "https://files.pythonhosted.org/packages/93/6c/34cda32cbe13adac5c6b291a307a5570d8ebd37c0c86339f0fe628e55632/ruff-0.17.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a", size = 10673624, upload-time = "2026-10-22T18:37:29.767Z" },
{ url = "https://files.pythonhosted.org/packages/bf/10/5d762a4dbc851d8bb47863cf3a28a61bf31c804d393b70d891fac899f5d1/ruff-0.17.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4", size = 11351797, upload-time = "2026-10-22T18:37:32.535Z" },
{ url = "https://files.pythonhosted.org/packages/3f/04/a0e92a81faf5c81c1c98d4374c8401734ccf2b8cca7d0170cb0fec08c6e2/ruff-0.17.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869", size = 10949414, upload-time = "2026-10-22T18:37:35.501Z" },
{ url = "https://files.pythonhosted.org/packages/c3/d8/59894cf630d2d86882457a2a6a0a1533410c5c805bfbac6f6eb4b5aef69d/ruff-0.17.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004", size = 10528000, upload-time = "2026-10-22T18:37:37.88Z" },
{ url = "https://files.pythonhosted.org/packages/ab/95/d6e30c51e851d37e10762ad91fa1e76ddaad998589b5667962a3cfbc9edd/ruff-0.17.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0", size = 10571779, upload-time = "2026-10-22T18:37:40.444Z" },
{ url = "https://files.pythonhosted.org/packages/f4/5a/a7e1d74fb464250137d3d4eca645d323536533b672004d04ee0e46e0caf3/ruff-0.17.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f", size = 10051707, upload-time = "2026-10-22T18:37:42.812Z" },
{ url = "https://files.pythonhosted.org/packages/6e/f2/02fca29ca7999380f0350a7212fe755c11ab9c22d61327356cce8decdbc6/ruff-0.17.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4", size = 9998227, upload-time = "2026-10-22T18:37:45.224Z" },
{ url = "https://files.pythonhosted.org/packages/d1/00/55aa604a093dfdc1526e570fae0bb7d37a2221c7b4728d5f080b2add16ba/ruff-0.17.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a", size = 10377692, upload-time = "2026-10-22T18:37:47.704Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5a/a7a640f47bb906ec8fa233ea84319d503e6dee02b59e3901bd312ebd1f9b/ruff-0.17.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe", size = 10790587, upload-time = "2026-10-22T18:37:50.191Z" },
{ url = "https://files.pythonhosted.org/packages/2c/d4/c3b74e1b4808438ea7f14bb914478025e2d51261f60774a54a0fa222ec46/ruff-0.17.2-py3-none-win32.whl", hash = "sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd", size = 9870537, upload-time = "2026-10-22T18:37:52.486Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/c7da96c2c96d20704cf9027f7a5121da272a8c05f1d1531ba949e3b3df04/ruff-0.17.2-py3-none-win_amd64.whl", hash = "sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c", size = 10282826, upload-time = "2026-10-22T18:37:55.2Z" },
{ url = "https://files.pythonhosted.org/packages/81/60/a3604c79cb02777b6cb84da894104021986f06c4bc41375102cc95049a32/ruff-0.17.2-py3-none-win_arm64.whl", hash = "sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962", size = 9827036, upload-time = "2026-10-22T18:37:58.031Z" },
]
[[package]]
@@ -2467,15 +2554,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.68.1"
version = "2.73.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/e7/c504a4bd2d95df2e0ab73714a9161ff1cf6ff1486922685e5f46dfd9eba8/sentry_sdk-2.68.1.tar.gz", hash = "sha256:6a97895230b04bc35d4d8d2e51e3b9e21902dfb0086ccf1f131a80c15c7b997a", size = 1019262, upload-time = "2026-08-24T13:09:38.108Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c5/3b/1e47acf2456781c26e12007379ecc154bf310b8f64d40390d78b791d55c3/sentry_sdk-2.73.0.tar.gz", hash = "sha256:4a2ab97b16e4693c325ab361c7bbbb346dbc7d0bc0150922d795a25bac8d5280", size = 1079078, upload-time = "2026-10-22T09:08:49.63Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/28/465ad9382be98f2172e691f5836cf87f936773913ad7ab85ba1ba1d6706e/sentry_sdk-2.68.1-py3-none-any.whl", hash = "sha256:775b78871783a0ffd758276ad01b3bb2b1ebcdad8f9d2f0a7723f76b73c99b65", size = 520851, upload-time = "2026-08-24T13:09:36.186Z" },
{ url = "https://files.pythonhosted.org/packages/b2/88/cabf98d017d7b940720e514e5f8ee02e3e6f7495caf93c30735a38387ec4/sentry_sdk-2.73.0-py3-none-any.whl", hash = "sha256:0bcfa6928ea5adbb89591858b514d27adac27c7a3cccc31b20ed10e0b09eda9e", size = 549011, upload-time = "2026-10-22T09:08:47.88Z" },
]
[[package]]
@@ -2539,14 +2626,14 @@ wheels = [
[[package]]
name = "types-requests"
version = "2.33.0.20260712"
version = "2.33.0.20261006"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/db/51/703318f7b7be8bee126ec13bf615050f932d0179b8784420f3a0199cc769/types_requests-2.33.0.20260712.tar.gz", hash = "sha256:2141b67ab534a5c5cd2dac5034f2a35f42e699c5bf185eee608c5246a069d7fb", size = 25084, upload-time = "2026-07-12T05:14:20.455Z" }
sdist = { url = "https://files.pythonhosted.org/packages/57/15/9b7e2e2e7c87d01366185b198b3febc6bc0c973f2bf21a62da4ab7d3495e/types_requests-2.33.0.20261006.tar.gz", hash = "sha256:0652999e9306aea345f40732d58fa49a7f6cade6a0d74d92119c5c8d82eddaf0", size = 25316, upload-time = "2026-10-06T08:15:57.782Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/e7/010c87f559e216d83f9dc51e939633fd0d0ead3377340181ab0e223cd3b5/types_requests-2.33.0.20260712-py3-none-any.whl", hash = "sha256:de027e28c171d3da529689cbfa023b0b4eab188c8dfa22fd834eebd2cee6e7bb", size = 21392, upload-time = "2026-07-12T05:14:19.616Z" },
{ url = "https://files.pythonhosted.org/packages/4d/72/b82789207b3d360ce9f5a4372c790c52cc3dd928aeb7d4faeec652b651b2/types_requests-2.33.0.20261006-py3-none-any.whl", hash = "sha256:26cc8146505cab33cda9737991929e4144c559bebe05078ccc6998f27c4ca2c1", size = 21445, upload-time = "2026-10-06T08:15:56.658Z" },
]
[[package]]
+4 -6
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine AS frontend-deps
FROM --platform=$BUILDPLATFORM node:22.23.4-alpine AS frontend-deps
RUN apk upgrade --no-cache ca-certificates
@@ -47,13 +47,11 @@ ENV VITE_MEDIA_BASE_URL=${VITE_MEDIA_BASE_URL}
RUN npm run build
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.30.5-alpine3.24 AS frontend-production
FROM nginx/nginx-unprivileged:1.30.6-alpine3.24 AS frontend-production
USER root
RUN apk upgrade --no-cache libexpat && \
apk add --no-cache --upgrade 'pcre2>=10.49-r0' 'tiff>=4.7.2-r0' \
'libx11>=1.8.14-r0' 'ca-certificates>=20261020-r0' && \
apk del curl
RUN apk del curl && \
rm -f /usr/share/nginx/html/50x.html
USER nginx
# Un-privileged user running the application
+13 -1
View File
@@ -11,11 +11,22 @@ server {
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|webp|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
location = / {
try_files /home.html /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
location = /home.html {
internal;
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
@@ -23,6 +34,7 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; # HTTP 1.0 header for backward compatibility
add_header Expires 0;
add_header X-Robots-Tag "noindex, nofollow" always;
}
# Optionally, handle 404 errors by redirecting to index.html
+1 -1
View File
@@ -3,6 +3,6 @@
"input": ["src/**/*.{ts,tsx}", "!src/styled-system/**/*", "!src/**/*.d.ts"],
"output": "src/locales/$LOCALE/$NAMESPACE.json",
"createOldCatalogs": false,
"locales": ["en", "fr", "de", "nl", "es"],
"locales": ["en", "fr", "de", "nl", "es", "pt"],
"sort": true
}
+1
View File
@@ -7,6 +7,7 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>%VITE_APP_TITLE%</title>
</head>
<body>
+20 -17
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.36.1",
"version": "1.37.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.36.1",
"version": "1.37.0",
"dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
"@fontsource-variable/lexend": "5.3.0",
@@ -21,14 +21,13 @@
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.4",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.34.1",
"i18next": "26.3.6",
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.3",
"livekit-client": "2.21.0",
"livekit-client": "2.23.0",
"posthog-js": "1.418.10",
"react": "18.3.1",
"react-aria": "3.51.0",
@@ -1028,9 +1027,9 @@
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.50.4",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.50.4.tgz",
"integrity": "sha512-L1uggNQAqyY21smQY8AllyOYbcv9Me9TaxwuLytL1R8ck9nbYPmQLNwEDi3pOFGAMa5F8I2nUi2Jc59W5awxlA==",
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.52.1.tgz",
"integrity": "sha512-X8DxKlNbzk6Lh+v630+mjdFHSotdAFwIyzxSRSUql2DbTxCnlpa7JsvFGc6Na3mXiex2OFh0iiQtWwOBP0rIYA==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
@@ -4563,12 +4562,6 @@
}
}
},
"node_modules/crisp-sdk-web": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/crisp-sdk-web/-/crisp-sdk-web-1.1.2.tgz",
"integrity": "sha512-qxSpiT4EGHnVEO6J/t1UURY6fg1ojgquLU0IIr7RNjZxXZs1R2e6AhAiFqxiGZ13Vb4o5VoUpATFU2TzX/ppOQ==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -8235,16 +8228,17 @@
"license": "MIT"
},
"node_modules/livekit-client": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.21.0.tgz",
"integrity": "sha512-RBUhPkV/sl1nzl8lokVlK5uATPwn0AlsudCBZXissw/kDl9yz8ac4pNJ43iPpMVoHOeBYH/BZ3vUC1adqa/zFQ==",
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.23.0.tgz",
"integrity": "sha512-j9Kg86nJjBjNFUD+ukp2jwMCGa0k+YD5gU//RzAM+ZY3pbG631QCte7fQjwqWMmtBzhDJL9nKYuOLMv/9IbAxg==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
"@livekit/protocol": "1.50.4",
"@livekit/protocol": "1.52.1",
"events": "^3.3.0",
"jose": "^6.1.0",
"loglevel": "^1.9.2",
"machina": "^7.0.0",
"sdp-transform": "^2.15.0",
"tslib": "2.8.1",
"typed-emitter": "^2.1.0",
@@ -8353,6 +8347,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/machina": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/machina/-/machina-7.0.1.tgz",
"integrity": "sha512-0Xkgs8NeBvO2hW6bjJT8ewLWrMfh1haMPstaghpASWjXkLjRhtDiaJG1Kb7mslaOHxZYxpH7PIjpmirRVYbMMA==",
"license": "MIT",
"engines": {
"node": ">=22.22"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+2 -3
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.36.1",
"version": "1.37.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -29,14 +29,13 @@
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.4",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.34.1",
"i18next": "26.3.6",
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.3",
"livekit-client": "2.21.0",
"livekit-client": "2.23.0",
"posthog-js": "1.418.10",
"react": "18.3.1",
"react-aria": "3.51.0",
+3
View File
@@ -36,6 +36,9 @@ const config: Config = {
jsxFramework: 'react',
outdir: 'src/styled-system',
globalFontface: {},
globalVars: {
'--global-font-body': 'var(--fonts-sans)',
},
theme: {
...pandaPreset.theme,
// media queries are defined in em so that zooming with text-only mode triggers breakpoints
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

+18
View File
@@ -0,0 +1,18 @@
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: ChatGPT-User
User-agent: ClaudeBot
User-agent: Claude-SearchBot
User-agent: Claude-User
User-agent: PerplexityBot
User-agent: Perplexity-User
User-agent: Google-Extended
User-agent: Applebot-Extended
User-agent: CCBot
User-agent: Meta-ExternalAgent
User-agent: Amazonbot
User-agent: Bytespider
Disallow: /
User-agent: *
Allow: /
+10 -1
View File
@@ -13,7 +13,9 @@ export interface ApiConfig {
flags_api_host?: string
}
support?: {
id: string
widget_script_url?: string
widget_api_url?: string
widget_channel?: string
help_article_transcript: string
help_article_recording: string
help_article_more_tools: string
@@ -73,6 +75,13 @@ export interface ApiConfig {
enable_firefox_proxy_workaround: boolean
default_sources: Source[]
default_video_codec: VideoCodec
reconnect?: {
backoff_enabled: boolean
base_delay_ms: number
max_delay_ms: number
give_up_after_ms: number
backoff_reset_ms: number
}
}
transcription_destination?: string
max_participants_for_sound: number
@@ -1,6 +1,5 @@
import { useConfig } from '@/api/useConfig'
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics'
import { useSupport } from '@/features/support/hooks/useSupport'
import { useSyncUserPreferencesWithBackend } from '@/features/auth/api/useSyncUserPreferencesWithBackend'
import { useEffect } from 'react'
@@ -8,10 +7,9 @@ export const AppInitialization = () => {
const { data } = useConfig()
useSyncUserPreferencesWithBackend()
const { analytics = {}, support = {}, custom_css_url = '' } = data ?? {}
const { analytics = {}, custom_css_url = '' } = data ?? {}
useAnalytics(analytics)
useSupport(support)
useEffect(() => {
if (custom_css_url) {
@@ -2,12 +2,6 @@ import type { CaptureResult } from 'posthog-js'
const IGNORED_EXCEPTION_PATTERNS = [
/ResizeObserver loop (completed with undelivered notifications|limit exceeded)/,
// livekit-client leaks the raw WebSocket error Event as an unhandled
// rejection when the signal ws errors after connect (Firefox-heavy,
// coincides with signal reconnects). Carries zero diagnostic content —
// the close reason is already logged by the SDK.
// See: https://github.com/livekit/client-sdk-js/issues/2062
/^Event captured as exception with keys: isTrusted$/,
]
const shouldIgnoreException = (value: unknown): boolean =>
@@ -32,6 +32,7 @@ export type LogCode =
| 'fullscreen_failure'
| 'publish_sources_failure'
| 'disconnect_failure'
| 'support_widget_failure'
| 'generic_failure'
export const reportError = (
@@ -1,5 +1,4 @@
import { apiUrl } from '@/api/apiUrl'
import { terminateSupportSession } from '@/features/support/hooks/useSupport'
import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics'
const logoutUrl = () => {
@@ -8,6 +7,5 @@ const logoutUrl = () => {
export const logout = async () => {
await terminateAnalyticsSession()
terminateSupportSession()
window.location.href = logoutUrl()
}
@@ -7,7 +7,11 @@ import {
} from 'react-aria-components'
import { RiArrowDownSFill } from '@remixicon/react'
import { css, cx } from '@/styled-system/css'
import type { ConnectionTestStepResult } from '../types'
import {
DIAGNOSTIC_OUTPUT_LANG,
getSummaryLang,
type ConnectionTestStepResult,
} from '../types'
import { StepStatusIndicator } from './StepStatusIndicator'
/** Each step is its own bounded card, collapsed or not. */
@@ -65,7 +69,7 @@ const labelClass = css({
const valueClass = css({
fontFamily: 'mono',
textStyle: 'xs',
color: 'greyscale.500',
color: 'greyscale.600',
overflowWrap: 'anywhere',
})
@@ -110,7 +114,11 @@ const StepRowContent = ({ step }: { step: ConnectionTestStepResult }) => {
<>
<span className={identityClass}>
<span className={labelClass}>{t(`steps.${step.id}`)}</span>
{step.summary && <span className={valueClass}>{step.summary}</span>}
{step.summary && (
<span className={valueClass} lang={getSummaryLang(step)}>
{step.summary}
</span>
)}
</span>
<StepStatusIndicator
status={step.status}
@@ -170,7 +178,7 @@ export const ConnectionTestStepRow = ({
{/* Collapsed panels stay in the DOM for aria-controls, but the log
lines themselves are only mounted when actually visible. */}
{isExpanded && (
<ul className={logListClass}>
<ul className={logListClass} lang={DIAGNOSTIC_OUTPUT_LANG}>
{step.logs?.map((log, index) => (
<li key={`${log.level}-${index}`} className={logItemClass}>
{log.message}
@@ -153,7 +153,7 @@ const counterValueClass = css({
})
/** A zero count is context, not a result: it recedes instead of shouting. */
const emptyCounterClass = css({ color: 'greyscale.400' })
const emptyCounterClass = css({ color: 'greyscale.600' })
const emptySquareClass = css({
backgroundColor: 'transparent!',
border: '1px solid {colors.greyscale.250}',
@@ -25,10 +25,10 @@ export const statusSquareClass: Record<ConnectionTestStepStatus, string> = {
* failure and warning.
*/
export const statusTextClass: Record<ConnectionTestStepStatus, string> = {
pending: css({ color: 'greyscale.500' }),
pending: css({ color: 'greyscale.600' }),
running: css({ color: 'greyscale.700' }),
success: css({ color: 'greyscale.1000' }),
warning: css({ color: 'warning', fontWeight: 'medium' }),
failed: css({ color: 'danger.600', fontWeight: 'medium' }),
skipped: css({ color: 'greyscale.500' }),
skipped: css({ color: 'greyscale.600' }),
}
@@ -136,14 +136,16 @@ const ConnectionTest = () => {
<h2 className={sectionTitleClass}>
{t(`groups.${group.id}`)}
</h2>
<div className={rowsClass}>
<ul className={rowsClass}>
{group.steps.map((id) => {
const step = stepsById.get(id)
return step ? (
<ConnectionTestStepRow key={id} step={step} />
<li key={id}>
<ConnectionTestStepRow step={step} />
</li>
) : null
})}
</div>
</ul>
</section>
))}
@@ -32,6 +32,23 @@ export type ConnectionTestStepResult = {
data?: Record<string, unknown>
}
export const DIAGNOSTIC_OUTPUT_LANG = 'en'
const LANGUAGE_NEUTRAL_STEPS: ReadonlySet<ConnectionTestStepId> = new Set([
'browser',
'microphone',
'camera',
'devices',
])
export const getSummaryLang = ({
id,
status,
}: Pick<ConnectionTestStepResult, 'id' | 'status'>) =>
LANGUAGE_NEUTRAL_STEPS.has(id) && status === 'success'
? undefined
: DIAGNOSTIC_OUTPUT_LANG
export type ConnectionTestGroupId = 'local' | 'network'
/** Display order: everything local first, then everything that leaves the machine. */
@@ -9,12 +9,8 @@ import { useState } from 'react'
import { menuRecipe } from '@/primitives/menuRecipe'
import { ApiRoom } from '@/features/rooms/api/ApiRoom'
import { useSnapshot } from 'valtio'
import { userStore } from '@/stores/user'
export const CreateMeetingMenu = () => {
const { username } = useSnapshot(userStore)
const { t } = useTranslation('home')
const { mutateAsync: createRoom } = useCreateRoom()
const [laterRoom, setLaterRoom] = useState<null | ApiRoom>(null)
@@ -30,9 +26,9 @@ export const CreateMeetingMenu = () => {
className={menuRecipe({ icon: true, variant: 'light' }).item}
onAction={() => {
const slug = generateRoomId()
createRoom({ slug, username }).then((data) =>
createRoom({ slug }).then((data) =>
navigateTo('room', data.slug, {
state: { create: true, initialRoomData: data },
state: { create: true },
})
)
}}
@@ -45,7 +41,7 @@ export const CreateMeetingMenu = () => {
className={menuRecipe({ icon: true, variant: 'light' }).item}
onAction={() => {
const slug = generateRoomId()
createRoom({ slug, username }).then(setLaterRoom)
createRoom({ slug }).then(setLaterRoom)
}}
data-attr="create-option-later"
>
@@ -1,6 +1,6 @@
import { styled } from '@/styled-system/jsx'
import { css } from '@/styled-system/css'
import { Button } from '@/primitives'
import { Button, Picture } from '@/primitives'
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -30,29 +30,32 @@ const Body = styled('p', {
},
})
const Image = styled('img', {
base: {
maxHeight: '362px',
height: '100%',
width: 'fit-content',
},
const image = css({
maxHeight: '362px',
height: '100%',
width: 'fit-content',
})
const Dot = styled('div', {
base: {
borderRadius: '50%',
borderStyle: 'solid',
borderWidth: '1px',
boxSizing: 'border-box',
display: 'inline-block',
height: '.375rem',
height: '.5rem',
margin: '0 .25rem',
width: '.375rem',
width: '.5rem',
},
variants: {
selected: {
true: {
backgroundColor: 'primary.800',
borderColor: 'primary.800',
},
false: {
backgroundColor: 'primary.300',
backgroundColor: 'primary.100',
borderColor: 'primary.500',
},
},
},
@@ -160,7 +163,6 @@ const carouselNavButton = css({
},
})
// todo - optimize how images are imported
const SLIDES: Slide[] = [
{
key: 'slide1',
@@ -246,7 +248,12 @@ export const IntroSlider = () => {
visible={index === slideIndex}
key={index}
>
<Image src={slide.src} alt="" role="presentation" />
<Picture
src={slide.src}
alt=""
role="presentation"
className={image}
/>
<TextAnimation visible={index == slideIndex}>
<Heading>{t(`${slide.key}.title`)}</Heading>
<Body>{t(`${slide.key}.body`)}</Body>
@@ -1,22 +1,21 @@
import { useTranslation } from 'react-i18next'
import { Field, Ul, H, P, Form, Dialog } from '@/primitives'
import { navigateTo } from '@/navigation/navigateTo'
import { isRoomValid } from '@/features/rooms'
import { extractRoomId } from '@/features/rooms'
export const JoinMeetingDialog = () => {
const { t } = useTranslation('home')
const handleSubmit = (data: { roomId?: FormDataEntryValue }) => {
const roomId = (data.roomId as string)
.trim()
.replace(`${window.location.origin}/`, '')
navigateTo('room', roomId)
const extracted = extractRoomId(data.roomId as string)
if (!extracted) return
navigateTo('room', `${extracted.roomId}${extracted.suffix}`)
}
const validateRoomId = (value: string) => {
const trimmed = value.trim()
if (!trimmed) return null
return !isRoomValid(trimmed) ? (
return !extractRoomId(trimmed) ? (
<>
<p>{t('joinInputError')}</p>
<Ul>
@@ -1,8 +1,10 @@
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { getScrollBarWidth } from '@livekit/components-core'
import * as React from 'react'
import { TrackLoop, useVisualStableUpdate } from '@livekit/components-react'
import { TrackLoop } from '@livekit/components-react'
import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
import { useCarouselTiles } from '../hooks/useCarouselTiles'
import { useSpeakerPromotionTrigger } from '../hooks/useSpeakerPromotionTrigger'
const MIN_HEIGHT = 130
const MIN_WIDTH = 140
@@ -23,8 +25,9 @@ export interface CarouselLayoutProps extends React.HTMLAttributes<HTMLMediaEleme
* The `CarouselLayout` component displays a list of tracks in a scroll container.
* It will display as many tiles as possible and overflow the rest.
* @remarks
* To ensure visual stability when tiles are reordered due to track updates,
* the component uses the `useVisualStableUpdate` hook.
* Tiles are ordered by `useCarouselTiles`: screen shares, new speakers and
* raised hands enter at the top, right below the local camera, and the other
* tiles keep their order.
* @example
* ```tsx
* const tracks = useTracks([Track.Source.Camera]);
@@ -66,7 +69,11 @@ export function CarouselLayout({
setPrevTiles(tilesThatFit)
}
const sortedTiles = useVisualStableUpdate(tracks, maxVisibleTiles)
const sortedTiles = useCarouselTiles(tracks, maxVisibleTiles)
// The carousel scrolls, so only the first tiles are guaranteed on screen.
useSpeakerPromotionTrigger(sortedTiles.slice(0, maxVisibleTiles), tracks, {
raisedHands: true,
})
React.useLayoutEffect(() => {
if (asideEl.current) {
@@ -46,7 +46,7 @@ export function GridLayout({ tracks, ...props }: GridLayoutProps) {
const { layout } = useGridLayout(gridEl, tracks.length)
const pagination = usePagination(layout.maxTiles, tracks)
useSpeakerPromotionTrigger(pagination.tracks)
useSpeakerPromotionTrigger(pagination.tracks, tracks)
useSwipe(gridEl, {
onLeftSwipe: pagination.nextPage,
@@ -0,0 +1,23 @@
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useRef } from 'react'
import { orderCarouselTiles } from '../utils/orderCarouselTiles'
/**
* Carousel counterpart of LiveKit's `useVisualStableUpdate`: same sort, same
* stable update, but screen shares and new speakers end up at the top. See
* `orderCarouselTiles`.
*
* Like `useVisualStableUpdate`, it only reorders when its host renders.
*/
export function useCarouselTiles(
tracks: TrackReferenceOrPlaceholder[],
maxVisibleTiles: number
) {
const previous = useRef<TrackReferenceOrPlaceholder[]>([])
previous.current = orderCarouselTiles(
previous.current,
tracks,
maxVisibleTiles
)
return previous.current
}
@@ -8,41 +8,65 @@ import { useEffect, useReducer, useRef } from 'react'
* Tripwire for speaker promotion.
*
* Listens to `RoomEvent.ActiveSpeakersChanged` imperatively and forces ONE
* re-render of the host component only when an active speaker has none of
* their tiles are within the visible span (`maxVisibleTiles`). That render
* re-runs `useVisualStableUpdate`, which reads live `participant.isSpeaking`
* state and performs the actual swap.
* re-render of the host when a speaker has a tile in `allTiles` but none in
* `visibleTiles`. That render re-runs the host's tile ordering, which reads
* live `participant.isSpeaking` state and performs the actual swap.
*
* Speakers already visible are ignored. Everything else costs zero React work.
* Speakers already visible cost nothing, nor do speakers the layout doesn't
* show at all (e.g. the pinned participant, rendered by the focus view).
*
* Requires the parent to NOT re-render on speaker events itself, i.e.
* With `raisedHands`, it also trips when a hand goes up or down while a
* raised hand is hidden: the hand that just went up, or the next one waiting
* for the slot of a hand that went down. Only for hosts whose ordering
* promotes raised hands; others would render for nothing.
*
* Requires the parent to NOT re-render on these events itself, i.e.
* `useTracks(..., { updateOnlyOn: [] })` upstream.
*/
export function useSpeakerPromotionTrigger(
sortedTiles: TrackReferenceOrPlaceholder[]
/** Tiles inside the span the layout guarantees to speakers. */
visibleTiles: TrackReferenceOrPlaceholder[],
/** Every tile the layout shows, visible or not. */
allTiles: TrackReferenceOrPlaceholder[],
{ raisedHands = false }: { raisedHands?: boolean } = {}
) {
const room = useRoomContext()
const [, forceRender] = useReducer((n: number) => n + 1, 0)
// Refs so the listener reads current values without re-subscribing
// and without itself being a render dependency.
const tilesRef = useRef(sortedTiles)
tilesRef.current = sortedTiles
// Read by the listeners at event time, so they never re-subscribe.
const tilesRef = useRef({ visibleTiles, allTiles })
tilesRef.current = { visibleTiles, allTiles }
useEffect(() => {
const onActiveSpeakersChanged = (speakers: Participant[]) => {
const tiles = tilesRef.current
const hiddenSpeakerExists = speakers.some(
(speaker) =>
!tiles.some((t) => t.participant.identity === speaker.identity)
const renderIfHidden = (participants: Participant[]) => {
const { visibleTiles, allTiles } = tilesRef.current
const visible = new Set(visibleTiles.map((t) => t.participant.identity))
const shown = new Set(allTiles.map((t) => t.participant.identity))
const hidden = participants.some(
({ identity }) => shown.has(identity) && !visible.has(identity)
)
if (hiddenSpeakerExists) {
if (hidden) {
forceRender()
}
}
room.on(RoomEvent.ActiveSpeakersChanged, onActiveSpeakersChanged)
return () => {
room.off(RoomEvent.ActiveSpeakersChanged, onActiveSpeakersChanged)
const onAttributesChanged = (changed: Record<string, string>) => {
if ('handRaisedAt' in changed) {
renderIfHidden(
tilesRef.current.allTiles
.map((t) => t.participant)
.filter((p) => !!p.attributes.handRaisedAt)
)
}
}
}, [room])
room.on(RoomEvent.ActiveSpeakersChanged, renderIfHidden)
if (raisedHands) {
room.on(RoomEvent.ParticipantAttributesChanged, onAttributesChanged)
}
return () => {
room.off(RoomEvent.ActiveSpeakersChanged, renderIfHidden)
room.off(RoomEvent.ParticipantAttributesChanged, onAttributesChanged)
}
}, [room, raisedHands])
}
@@ -0,0 +1,201 @@
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { Track } from 'livekit-client'
import { describe, expect, it } from 'vitest'
import { orderCarouselTiles } from './orderCarouselTiles'
type Tile = TrackReferenceOrPlaceholder
// Placeholder tiles are enough: the real LiveKit sort and update run on them.
const tile = (
identity: string,
joinedAt: number,
{
source = Track.Source.Camera,
isLocal = false,
isSpeaking = false,
hasSpoken = isSpeaking,
handRaisedAt,
}: {
source?: Track.Source
isLocal?: boolean
isSpeaking?: boolean
hasSpoken?: boolean
handRaisedAt?: string
} = {}
) =>
({
participant: {
identity,
isLocal,
isSpeaking,
audioLevel: isSpeaking ? 1 : 0,
lastSpokeAt: hasSpoken ? new Date(joinedAt) : undefined,
joinedAt: new Date(joinedAt),
isCameraEnabled: false,
attributes: handRaisedAt ? { handRaisedAt } : {},
},
source,
}) as unknown as Tile
const names = (tiles: Tile[]) =>
tiles.map((t) =>
t.source === Track.Source.ScreenShare
? `${t.participant.identity}:screen`
: t.participant.identity
)
const me = tile('me', 0, { isLocal: true })
const [a, b, c, d, e] = ['a', 'b', 'c', 'd', 'e'].map((id, i) =>
tile(id, i + 1)
)
const speaking = (t: Tile) =>
tile(t.participant.identity, t.participant.joinedAt!.getTime(), {
isSpeaking: true,
})
const quietAgain = (t: Tile) =>
tile(t.participant.identity, t.participant.joinedAt!.getTime(), {
hasSpoken: true,
})
const handUp = (t: Tile, minute = 0) =>
tile(t.participant.identity, t.participant.joinedAt!.getTime(), {
isLocal: t.participant.isLocal,
handRaisedAt: new Date(Date.UTC(2026, 0, 1, 10, minute)).toISOString(),
})
const screenOf = (t: Tile) =>
tile(t.participant.identity, t.participant.joinedAt!.getTime(), {
source: Track.Source.ScreenShare,
})
describe('orderCarouselTiles', () => {
it('follows the LiveKit sort on the first render', () => {
expect(names(orderCarouselTiles([], [d, c, b, a, me], 3))).toEqual([
'me',
'a',
'b',
'c',
'd',
])
})
it('puts a new speaker right below the local camera', () => {
const previous = orderCarouselTiles([], [me, a, b, c, d, e], 4)
const next = orderCarouselTiles(previous, [me, a, b, c, d, speaking(e)], 4)
// LiveKit alone would give me, a, b, e: `e` takes the slot of `c`.
expect(names(next).slice(0, 4)).toEqual(['me', 'e', 'a', 'b'])
})
it('keeps the order of the tiles that stay visible', () => {
let tiles = orderCarouselTiles([], [me, a, b, c, d, e], 4)
tiles = orderCarouselTiles(tiles, [me, a, b, c, speaking(d), e], 4)
expect(names(tiles).slice(0, 4)).toEqual(['me', 'd', 'a', 'b'])
// `d` went quiet, `e` speaks: `d` stays ahead of `a`, `b` is evicted.
tiles = orderCarouselTiles(
tiles,
[me, a, b, c, quietAgain(d), speaking(e)],
4
)
expect(names(tiles).slice(0, 4)).toEqual(['me', 'e', 'd', 'a'])
})
it('does not move a visible participant who speaks', () => {
const previous = orderCarouselTiles([], [me, a, b, c, d], 4)
const next = orderCarouselTiles(previous, [me, a, b, speaking(c), d], 4)
expect(names(next)).toEqual(['me', 'a', 'b', 'c', 'd'])
})
it('keeps screen shares above a new speaker', () => {
const previous = orderCarouselTiles([], [me, screenOf(a), a, b, c, d], 4)
expect(names(previous).slice(0, 4)).toEqual(['me', 'a:screen', 'a', 'b'])
const next = orderCarouselTiles(
previous,
[me, screenOf(a), a, b, c, speaking(d)],
4
)
expect(names(next).slice(0, 4)).toEqual(['me', 'a:screen', 'd', 'a'])
})
it('puts a screen share that starts below the local camera', () => {
const previous = orderCarouselTiles([], [me, a, b, c, d], 4)
const next = orderCarouselTiles(previous, [me, a, b, c, d, screenOf(d)], 4)
expect(names(next).slice(0, 4)).toEqual(['me', 'd:screen', 'a', 'b'])
})
it('fills the slot of a participant who left without reordering', () => {
const previous = orderCarouselTiles([], [me, a, b, c, d], 4)
const next = orderCarouselTiles(previous, [me, b, c, d], 4)
expect(names(next)).toEqual(['me', 'd', 'b', 'c'])
})
it('brings a raised hand to the top like a speaker', () => {
const previous = orderCarouselTiles([], [me, a, b, c, d, e], 4)
const next = orderCarouselTiles(previous, [me, a, b, c, d, handUp(e)], 4)
expect(names(next).slice(0, 4)).toEqual(['me', 'e', 'a', 'b'])
})
it('keeps a raised hand visible when others speak', () => {
let tiles = orderCarouselTiles([], [me, a, b, c, d, e], 3)
tiles = orderCarouselTiles(tiles, [me, a, b, c, d, handUp(e)], 3)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'e', 'a'])
// `d` speaks: it evicts `a`, not the raised hand.
tiles = orderCarouselTiles(tiles, [me, a, b, c, speaking(d), handUp(e)], 3)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'd', 'e'])
})
it('lets the current speaker in when hands fill the carousel', () => {
let tiles = orderCarouselTiles([], [me, handUp(a, 1), handUp(b, 2), c], 3)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'a', 'b'])
// The last hand raised gives way to the speaker.
tiles = orderCarouselTiles(
tiles,
[me, handUp(a, 1), handUp(b, 2), speaking(c)],
3
)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'c', 'a'])
})
it('gives the slot of a lowered hand to the next one waiting', () => {
let tiles = orderCarouselTiles(
[],
[me, handUp(a, 1), handUp(b, 2), handUp(c, 3)],
3
)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'a', 'b'])
// `a` lowers their hand: `c` was waiting and takes over.
tiles = orderCarouselTiles(tiles, [me, a, handUp(b, 2), handUp(c, 3)], 3)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'c', 'b'])
})
it('shows the first hands raised when there are too many', () => {
const tiles = orderCarouselTiles(
[],
[me, a, handUp(b, 3), handUp(c, 1), handUp(d, 2)],
3
)
expect(names(tiles).slice(0, 3)).toEqual(['me', 'c', 'd'])
})
it('keeps the local camera first when the local hand is raised', () => {
const tiles = orderCarouselTiles([], [a, screenOf(a), b, handUp(me)], 3)
expect(names(tiles)).toEqual(['me', 'a:screen', 'a', 'b'])
})
it('is stable when run twice on the same input', () => {
const tracks = [me, a, b, c, d, speaking(e)]
const previous = orderCarouselTiles([], [me, a, b, c, d, e], 4)
const once = orderCarouselTiles(previous, tracks, 4)
expect(names(orderCarouselTiles(once, tracks, 4))).toEqual(names(once))
})
})
@@ -0,0 +1,88 @@
import {
log,
sortTrackReferences,
updatePages,
type TrackReferenceOrPlaceholder,
} from '@livekit/components-core'
import { Track } from 'livekit-client'
// LiveKit's track reference id changes when a placeholder gets its track;
// this key doesn't, so turning a camera on is not seen as a new tile.
const keyOf = (tile: TrackReferenceOrPlaceholder) =>
`${tile.participant.identity}_${tile.source}`
/** When the participant raised their hand, if it is still raised. */
const handRaisedAt = (tile: TrackReferenceOrPlaceholder) => {
if (tile.source !== Track.Source.Camera) return undefined
const time = Date.parse(tile.participant.attributes.handRaisedAt ?? '')
return Number.isNaN(time) ? undefined : time
}
/**
* LiveKit's sort knows nothing about raised hands. This moves them right
* after the tiles that must stay ahead (local camera, screen shares, whoever
* is speaking now), first raised first, so they are among the first tiles
* picked as visible for as long as the hand is up.
*/
const sortWithRaisedHands = (tracks: TrackReferenceOrPlaceholder[]) => {
const rank = (tile: TrackReferenceOrPlaceholder) => {
const { isLocal, isSpeaking } = tile.participant
if (isLocal || isSpeaking || tile.source === Track.Source.ScreenShare) {
return 0
}
return handRaisedAt(tile) === undefined ? 2 : 1
}
return sortTrackReferences(tracks).sort((a, b) => {
if (rank(a) !== rank(b)) return rank(a) - rank(b)
// Equal ranks keep LiveKit's order, except raised hands: first come first.
return rank(a) === 1 ? handRaisedAt(a)! - handRaisedAt(b)! : 0
})
}
/**
* Orders the carousel tiles; the first `maxVisibleTiles` are the visible ones.
*
* This is what LiveKit's `useVisualStableUpdate` does, plus one step:
* 1. `sortTrackReferences` decides which tiles deserve to be visible, with
* raised hands added to it, see `sortWithRaisedHands`;
* 2. `updatePages` applies that to the `previous` order with as few moves as
* possible: an incoming tile is swapped with the outgoing one;
* 3. added here: within the visible tiles, the local camera goes first, then
* the screen shares, then the speakers and raised hands that step 2 just
* brought in. The other tiles keep the order step 2 gave them.
*
* Step 2 alone leaves an incoming tile wherever the evicted one happened to
* be, often at the bottom of the column.
*/
export function orderCarouselTiles(
previous: TrackReferenceOrPlaceholder[],
tracks: TrackReferenceOrPlaceholder[],
maxVisibleTiles: number
): TrackReferenceOrPlaceholder[] {
const sorted = sortWithRaisedHands(tracks)
let stable = sorted
try {
stable = updatePages(previous, sorted, maxVisibleTiles)
} catch (error) {
log.error('Error while running updatePages(): ', error)
}
const previouslyVisible = new Set(
previous.slice(0, maxVisibleTiles).map(keyOf)
)
const rank = (tile: TrackReferenceOrPlaceholder) => {
if (tile.source === Track.Source.ScreenShare) return 1
if (tile.participant.isLocal) return 0
const justCameIn = !previouslyVisible.has(keyOf(tile))
const wantsAttention =
tile.participant.isSpeaking || handRaisedAt(tile) !== undefined
return justCameIn && wantsAttention ? 2 : 3
}
return [
// Array.prototype.sort is stable: tiles of equal rank keep their order.
...stable.slice(0, maxVisibleTiles).sort((a, b) => rank(a) - rank(b)),
...stable.slice(maxVisibleTiles),
]
}
@@ -96,6 +96,8 @@ export const MainNotificationToast = () => {
case NotificationType.ScreenRecordingStopped:
case NotificationType.TranscriptionLimitReached:
case NotificationType.ScreenRecordingLimitReached:
case NotificationType.TranscriptionInterrupted:
case NotificationType.ScreenRecordingInterrupted:
case NotificationType.TranscriptionFailed:
case NotificationType.ScreenRecordingFailed:
case NotificationType.TranscriptionAborted:
@@ -10,6 +10,7 @@ export enum NotificationType {
TranscriptionStarted = 'transcriptionStarted',
TranscriptionStopped = 'transcriptionStopped',
TranscriptionLimitReached = 'transcriptionLimitReached',
TranscriptionInterrupted = 'transcriptionInterrupted',
TranscriptionFailed = 'transcriptionFailed',
TranscriptionAborted = 'transcriptionAborted',
TranscriptionRequested = 'transcriptionRequested',
@@ -17,6 +18,7 @@ export enum NotificationType {
ScreenRecordingStopped = 'screenRecordingStopped',
ScreenRecordingRequested = 'screenRecordingRequested',
ScreenRecordingLimitReached = 'screenRecordingLimitReached',
ScreenRecordingInterrupted = 'screenRecordingInterrupted',
ScreenRecordingFailed = 'screenRecordingFailed',
ScreenRecordingAborted = 'screenRecordingAborted',
RecordingSaving = 'recordingSaving',
@@ -22,6 +22,8 @@ export function ToastAnyRecording({ state, ...props }: Readonly<ToastProps>) {
return 'transcript.stopped'
case NotificationType.TranscriptionLimitReached:
return 'transcript.limitReached'
case NotificationType.TranscriptionInterrupted:
return 'transcript.interrupted'
case NotificationType.TranscriptionFailed:
return 'transcript.failed'
case NotificationType.TranscriptionAborted:
@@ -32,6 +34,8 @@ export function ToastAnyRecording({ state, ...props }: Readonly<ToastProps>) {
return 'screenRecording.stopped'
case NotificationType.ScreenRecordingLimitReached:
return 'screenRecording.limitReached'
case NotificationType.ScreenRecordingInterrupted:
return 'screenRecording.interrupted'
case NotificationType.ScreenRecordingFailed:
return 'screenRecording.failed'
case NotificationType.ScreenRecordingAborted:

Some files were not shown because too many files have changed in this diff Show More