markitzeroo commited on
Commit
cfaaa6c
·
verified ·
1 Parent(s): 85093b6

Initial deploy: Dockerized FastAPI + React frontend

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +15 -0
  2. .gitattributes +15 -0
  3. .gitignore +25 -0
  4. Dockerfile +40 -0
  5. README.md +388 -10
  6. backend/.venv/Lib/site-packages/_distutils_hack/__init__.py +222 -0
  7. backend/.venv/Lib/site-packages/_distutils_hack/override.py +1 -0
  8. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER +1 -0
  9. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/METADATA +145 -0
  10. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/RECORD +12 -0
  11. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/REQUESTED +0 -0
  12. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/WHEEL +4 -0
  13. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt +4 -0
  14. backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE +21 -0
  15. backend/.venv/Lib/site-packages/annotated_doc/__init__.py +3 -0
  16. backend/.venv/Lib/site-packages/annotated_doc/main.py +36 -0
  17. backend/.venv/Lib/site-packages/annotated_doc/py.typed +0 -0
  18. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER +1 -0
  19. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA +295 -0
  20. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD +11 -0
  21. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/REQUESTED +0 -0
  22. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL +4 -0
  23. backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE +21 -0
  24. backend/.venv/Lib/site-packages/annotated_types/__init__.py +432 -0
  25. backend/.venv/Lib/site-packages/annotated_types/py.typed +0 -0
  26. backend/.venv/Lib/site-packages/annotated_types/test_cases.py +151 -0
  27. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/INSTALLER +1 -0
  28. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/METADATA +96 -0
  29. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/RECORD +93 -0
  30. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/REQUESTED +0 -0
  31. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/WHEEL +5 -0
  32. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/entry_points.txt +2 -0
  33. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/licenses/LICENSE +20 -0
  34. backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/top_level.txt +1 -0
  35. backend/.venv/Lib/site-packages/anyio/__init__.py +111 -0
  36. backend/.venv/Lib/site-packages/anyio/_backends/__init__.py +0 -0
  37. backend/.venv/Lib/site-packages/anyio/_backends/_asyncio.py +0 -0
  38. backend/.venv/Lib/site-packages/anyio/_backends/_trio.py +1384 -0
  39. backend/.venv/Lib/site-packages/anyio/_core/__init__.py +0 -0
  40. backend/.venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py +167 -0
  41. backend/.venv/Lib/site-packages/anyio/_core/_contextmanagers.py +200 -0
  42. backend/.venv/Lib/site-packages/anyio/_core/_eventloop.py +229 -0
  43. backend/.venv/Lib/site-packages/anyio/_core/_exceptions.py +153 -0
  44. backend/.venv/Lib/site-packages/anyio/_core/_fileio.py +797 -0
  45. backend/.venv/Lib/site-packages/anyio/_core/_resources.py +18 -0
  46. backend/.venv/Lib/site-packages/anyio/_core/_signals.py +27 -0
  47. backend/.venv/Lib/site-packages/anyio/_core/_sockets.py +991 -0
  48. backend/.venv/Lib/site-packages/anyio/_core/_streams.py +52 -0
  49. backend/.venv/Lib/site-packages/anyio/_core/_subprocesses.py +202 -0
  50. backend/.venv/Lib/site-packages/anyio/_core/_synchronization.py +753 -0
.dockerignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ backend/__pycache__
4
+ backend/.deepinfra_api_key
5
+ knowledge_contexts
6
+ test_logs
7
+ transcript_logs
8
+ *.log
9
+ .git
10
+ .vscode
11
+ .idea
12
+ *.bat
13
+ README.md
14
+ workflows
15
+ scenarios
.gitattributes CHANGED
@@ -33,3 +33,18 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ backend/.venv/Lib/site-packages/pip/_vendor/distlib/t64-arm.exe filter=lfs diff=lfs merge=lfs -text
37
+ backend/.venv/Lib/site-packages/pip/_vendor/distlib/t64.exe filter=lfs diff=lfs merge=lfs -text
38
+ backend/.venv/Lib/site-packages/pip/_vendor/distlib/w64-arm.exe filter=lfs diff=lfs merge=lfs -text
39
+ backend/.venv/Lib/site-packages/pip/_vendor/distlib/w64.exe filter=lfs diff=lfs merge=lfs -text
40
+ backend/.venv/Lib/site-packages/pydantic_core/_pydantic_core.cp310-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
41
+ backend/.venv/Lib/site-packages/setuptools/cli-arm64.exe filter=lfs diff=lfs merge=lfs -text
42
+ backend/.venv/Lib/site-packages/setuptools/gui-arm64.exe filter=lfs diff=lfs merge=lfs -text
43
+ backend/.venv/Scripts/fastapi.exe filter=lfs diff=lfs merge=lfs -text
44
+ backend/.venv/Scripts/httpx.exe filter=lfs diff=lfs merge=lfs -text
45
+ backend/.venv/Scripts/pip.exe filter=lfs diff=lfs merge=lfs -text
46
+ backend/.venv/Scripts/pip3.10.exe filter=lfs diff=lfs merge=lfs -text
47
+ backend/.venv/Scripts/pip3.exe filter=lfs diff=lfs merge=lfs -text
48
+ backend/.venv/Scripts/python.exe filter=lfs diff=lfs merge=lfs -text
49
+ backend/.venv/Scripts/pythonw.exe filter=lfs diff=lfs merge=lfs -text
50
+ backend/.venv/Scripts/uvicorn.exe filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ dist/
3
+ backend/.venv/
4
+ backend/__pycache__/
5
+ backend/**/*.pyc
6
+
7
+ .env
8
+ .env.*
9
+ !.env.example
10
+ backend/.deepinfra_api_key
11
+
12
+ knowledge_contexts/
13
+ test_logs/
14
+ transcript_logs/
15
+
16
+ .vite/
17
+ *.local
18
+ *.log
19
+ npm-debug.log*
20
+ yarn-debug.log*
21
+ yarn-error.log*
22
+ pnpm-debug.log*
23
+
24
+ .DS_Store
25
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM node:20-alpine AS frontend
4
+ WORKDIR /app
5
+ COPY package.json package-lock.json ./
6
+ RUN npm ci
7
+ COPY vite.config.js index.html ./
8
+ COPY src ./src
9
+ COPY public ./public
10
+ RUN npm run build && \
11
+ printf '{"backendUrl": ""}\n' > /app/dist/config.json
12
+
13
+ FROM python:3.12-slim AS runtime
14
+ ENV PYTHONDONTWRITEBYTECODE=1 \
15
+ PYTHONUNBUFFERED=1 \
16
+ PIP_NO_CACHE_DIR=1 \
17
+ PORT=7860 \
18
+ LLM_PROVIDER=deepinfra \
19
+ HOME=/home/app
20
+
21
+ RUN useradd -m -u 1000 -d /home/app app
22
+
23
+ WORKDIR /app
24
+
25
+ COPY backend/requirements.txt ./backend/requirements.txt
26
+ RUN pip install --no-cache-dir -r backend/requirements.txt
27
+
28
+ COPY backend ./backend
29
+ COPY public ./public
30
+ COPY --from=frontend /app/dist ./dist
31
+
32
+ RUN mkdir -p /app/knowledge_contexts /app/test_logs /app/transcript_logs && \
33
+ chown -R app:app /app
34
+
35
+ USER app
36
+
37
+ EXPOSE 7860
38
+
39
+ WORKDIR /app/backend
40
+ CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-7860}"]
README.md CHANGED
@@ -1,10 +1,388 @@
1
- ---
2
- title: Nodes Ui Flow
3
- emoji: 📚
4
- colorFrom: yellow
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Nodes UI Flow
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ ---
10
+
11
+ # Nodes UI Flow
12
+
13
+ Визуальный редактор интерактивных AI-сценариев: сценарий собирается из нод, запускается пошагово, задает вопросы пользователю, сохраняет ответы в память, ветвится по смыслу и может отвечать по knowledge-файлам.
14
+
15
+ Проект сейчас содержит пример для RosUpack 2026, но ноды сделаны универсальными: такой же граф можно собрать для другого стенда, продукта или процесса.
16
+
17
+ ## Что внутри
18
+
19
+ - React + React Flow интерфейс для сборки графов.
20
+ - FastAPI backend для LLM-запросов, классификации, извлечения памяти и ответов по файлам.
21
+ - Выбор LLM provider в настройках: Ollama или DeepInfra.
22
+ - Перефразирование реплик ассистента при запуске нод, чтобы сценарий не звучал одинаково каждый раз.
23
+ - `LLM Test` режим: LLM сама проходит сценарий, а evaluator проверяет вопросы и ответы ассистента.
24
+ - Сохранение workflow в JSON.
25
+ - Knowledge-файлы, привязанные к конкретной `Knowledge Answer` ноде.
26
+
27
+ ## Быстрый запуск на Windows
28
+
29
+ Ollama запускается отдельно вручную.
30
+
31
+ ```bat
32
+ start_interface.bat
33
+ ```
34
+
35
+ Скрипт откроет два окна:
36
+
37
+ - backend: `http://localhost:8000`
38
+ - frontend: `http://localhost:5173`
39
+
40
+ Остановить интерфейс можно закрытием этих двух окон.
41
+
42
+ ## Ручной запуск
43
+
44
+ Backend:
45
+
46
+ ```powershell
47
+ cd backend
48
+ python -m venv .venv
49
+ .\.venv\Scripts\activate
50
+ pip install -r requirements.txt
51
+ python main.py
52
+ ```
53
+
54
+ Frontend:
55
+
56
+ ```powershell
57
+ npm install
58
+ npm run dev
59
+ ```
60
+
61
+ Production build:
62
+
63
+ ```powershell
64
+ npm run build
65
+ ```
66
+
67
+ ## Структура проекта
68
+
69
+ ```text
70
+ backend/ FastAPI backend
71
+ public/ frontend config and presets
72
+ scenarios/
73
+ dialog/ markdown versions of scenario scripts
74
+ knowledge/ curated knowledge bases for Knowledge Answer nodes
75
+ source/ original source documents
76
+ src/ React app, nodes and workflow runtime
77
+ workflows/ saved workflow JSON graphs
78
+ archive/ old or experimental workflow snapshots
79
+ start_interface.bat one-click Windows launcher
80
+ ```
81
+
82
+ Runtime/generated folders are intentionally ignored by git:
83
+
84
+ ```text
85
+ dist/
86
+ node_modules/
87
+ backend/.venv/
88
+ knowledge_contexts/
89
+ test_logs/
90
+ transcript_logs/
91
+ ```
92
+
93
+ ## Основной workflow
94
+
95
+ Главный пример:
96
+
97
+ ```text
98
+ workflows/rosupack_2026_workflow.json
99
+ ```
100
+
101
+ Knowledge base для него:
102
+
103
+ ```text
104
+ scenarios/knowledge/rosupack_2026_knowledge_base.md
105
+ ```
106
+
107
+ В `Knowledge Answer` ноде путь к файлу сохраняется прямо в JSON workflow, поэтому граф можно открыть после clone и продолжить использовать без повторного upload, если файл лежит в репозитории.
108
+
109
+ ## Как устроен интерактивный сценарий
110
+
111
+ Типичный поток:
112
+
113
+ ```text
114
+ Question -> Save Memory -> Assistant Message -> Semantic Branch -> Question/Message/... -> Knowledge Answer -> Restart
115
+ ```
116
+
117
+ Runtime идет до первой `Question`, показывает вопрос в нижней панели и ждет ответ пользователя. Ответ можно:
118
+
119
+ - ввести руками;
120
+ - сгенерировать кнопкой `LLM answer`;
121
+ - включить `Auto LLM answer`, чтобы LLM отвечала сама на каждый новый вопрос.
122
+
123
+ Если у ноды включена галочка `Перефразировать при запуске`, backend перед показом реплики отправит текст в выбранный LLM provider и вернет новую формулировку с тем же смыслом. В transcript и дальше по графу попадет именно показанная пользователю версия.
124
+
125
+ ## LLM provider
126
+
127
+ Provider выбирается в `Настройки`:
128
+
129
+ - `Ollama` — локальн��й режим, backend обращается к `http://localhost:11434`.
130
+ - `DeepInfra` — облачный OpenAI-compatible endpoint.
131
+
132
+ Для DeepInfra в настройках появляется поле API key. Ключ сохраняется локально в `backend/.deepinfra_api_key`, этот файл игнорируется git. Если ключ уже сохранен, поле можно оставить пустым, чтобы не менять его.
133
+
134
+ Thinking/reasoning в LLM-вызовах отключен: для Ollama backend отправляет `think: false`, для DeepInfra используется `reasoning_effort: none`.
135
+
136
+ ## Основные типы портов
137
+
138
+ Порты окрашиваются по типу/смыслу, а не по цвету ноды.
139
+
140
+ - `turn` — объект одного шага диалога: вопрос, ответ и накопленный dialog.
141
+ - `dialog` / `dialog-in` — история сообщений.
142
+ - `question` — текст вопроса.
143
+ - `answer` — текст ответа пользователя.
144
+ - `string` — обычный текст.
145
+ - `object` — структурированные данные.
146
+
147
+ Один input может принимать несколько входящих связей. Это используется, когда несколько веток сходятся в общий блок, например в общий `Еще вопросы?`.
148
+
149
+ ## Ключевые ноды
150
+
151
+ ### Question
152
+
153
+ Задает вопрос ассистента и ставит workflow на паузу до ответа пользователя.
154
+
155
+ Используйте `turn` output, если дальше нужна вся пара вопрос/ответ. Используйте `dialog` или `dialog-in`, если важно сохранить историю реплик.
156
+
157
+ Опция `Перефразировать при запуске` заставляет LLM переписать вопрос перед показом пользователю.
158
+
159
+ ### Assistant Message
160
+
161
+ Показывает реплику ассистента без ожидания пользовательского ответа.
162
+
163
+ Подходит для коротких объяснений, переходов между ветками и финальных историй.
164
+
165
+ Опция `Перефразировать при запуске` работает так же, как в `Question`: смысл сохраняется, формулировка может меняться.
166
+
167
+ ### Save Memory
168
+
169
+ Извлекает значение из ответа пользователя и сохраняет его в память прогона.
170
+
171
+ Пример ключей:
172
+
173
+ ```text
174
+ name
175
+ role
176
+ industry
177
+ scale
178
+ ```
179
+
180
+ Значения доступны в шаблонах через фигурные скобки:
181
+
182
+ ```text
183
+ {name}, хотели бы узнать что-то еще?
184
+ ```
185
+
186
+ Если перед `Save Memory` был retry-переспрос, нода берет последний ответ пользователя из `dialog-in`, а не старый первый ответ из `turn`. Это важно для сценариев вроде проверки имени: пользователь может сначала ответить бессмыслицу, затем назвать имя, и в `{name}` сохранится именно последний корректный ответ.
187
+
188
+ ### Semantic Branch
189
+
190
+ LLM выбирает один выход по смыслу ответа пользователя.
191
+
192
+ Один choice = один output. Альтернативы внутри одного choice пишутся через точку с запятой:
193
+
194
+ ```text
195
+ да; хочу еще; давай; есть вопрос
196
+ нет; хватит; вернуться в начало
197
+ ```
198
+
199
+ Точка с запятой означает альтернативные формулировки, а не условие, что все фразы должны встретиться одновременно.
200
+
201
+ Если ответ непонятен, `retry on unclear` может сразу переспросить пользователя на этой же точке сценария.
202
+
203
+ У retry-вопроса есть отдельная галочка `Перефразировать unclear-вопрос`, чтобы повторный вопрос звучал живее при нескольких неудачных ответах подряд.
204
+
205
+ ### Knowledge Answer
206
+
207
+ Отвечает на вопрос пользователя по файлу, привязанному к конкретной ноде.
208
+
209
+ Поддерживаются:
210
+
211
+ - `.md`
212
+ - `.txt`
213
+ - `.docx`
214
+ - `.doc` как best-effort text extraction
215
+
216
+ При upload backend сохраняет оригинал и extracted text. Для репозиторных сценариев лучше хранить curated `.md` knowledge base в `scenarios/knowledge/` и указывать этот относительный путь в `contextPath`.
217
+
218
+ ### Restart
219
+
220
+ Перезапускает интерактивный сценарий с начала.
221
+
222
+ Удобно для выставочного сценария: если пользователь больше не хочет задавать вопросы, можно вернуть ассистента в стартовое состояние.
223
+
224
+ ### Request to LLM
225
+
226
+ Отправляет `system` и `user` prompt на backend.
227
+
228
+ Используйте для универсальных LLM-операций, которые не покрыты готовыми специализированными нодами.
229
+
230
+ ### Component
231
+
232
+ Сворачивает часть графа во вложенный компонент.
233
+
234
+ Подходит для повторяющихся блоков, когда сценарий начинает разрастаться.
235
+
236
+ ## Как соединять ноды
237
+
238
+ ### Сохранить имя
239
+
240
+ ```text
241
+ Question(turn) -> Semantic Branch(turn)
242
+ Question(turn) -> Save Memory(turn)
243
+ Semantic Branch(has-name) -> Save Memory(dialog-in)
244
+ Save Memory(dialog) -> Assistant Message(dialog-in)
245
+ ```
246
+
247
+ `Semantic Branch` проверяет, есть ли в ответе имя. Если ответа недостаточно, `retry on unclear` сразу переспросит. `Save Memory` получает старый `turn` и свежий `dialog-in`; приоритет отдается последнему ответу пользователя из `dialog-in`, поэтому после переспроса сохраняется корректное имя.
248
+
249
+ ### Ветвление по смыслу
250
+
251
+ ```text
252
+ Question(turn) -> Semantic Branch(turn)
253
+ Semantic Branch(choice A) -> Node A(dialog-in)
254
+ Semantic Branch(choice B) -> Node B(dialog-in)
255
+ Semantic Branch(unclear) -> retry или fallback
256
+ ```
257
+
258
+ ### Общий финальный блок
259
+
260
+ ```text
261
+ Branch A final message(dialog) -> Shared Question(dialog-in)
262
+ Branch B final message(dialog) -> Shared Question(dialog-in)
263
+ Branch C final message(dialog) -> Shared Question(dialog-in)
264
+ ```
265
+
266
+ Так несколько веток сходятся в один общий вопрос без дублирования нод.
267
+
268
+ ### Зацикливание knowledge-вопросов
269
+
270
+ ```text
271
+ Question "Что узнать?"(turn) -> Knowledge Answer(turn)
272
+ Knowledge Answer(dialog) -> Question "Еще вопросы?"(dialog-in)
273
+ ```
274
+
275
+ Так пользователь может задавать вопросы по файлу, пока не ответит, что больше вопросов нет.
276
+
277
+ ## LLM answer и LLM Test
278
+
279
+ ### LLM answer
280
+
281
+ Разово генерирует ответ пользователя на текущий вопрос.
282
+
283
+ ### Auto LLM answer
284
+
285
+ Автоматически отвечает на каждый новый вопрос тем же prompt-ом тестового пользователя.
286
+
287
+ Prompt хранится в workflow JSON:
288
+
289
+ ```json
290
+ {
291
+ "settings": {
292
+ "llmRolePrompt": "..."
293
+ }
294
+ }
295
+ ```
296
+
297
+ ### LLM Test
298
+
299
+ Отдельный режим тестирования сценария:
300
+
301
+ 1. LLM проходит сценарий как пользователь.
302
+ 2. После вопросов/ответов ассистента evaluator проверяет адекватность.
303
+ 3. Если evaluator видит проблему, в JSONL-лог первым событием пишется причина.
304
+ 4. Полный trace LLM-запросов сохраняется в `test_logs/`.
305
+
306
+ `LLM Test` можно остановить кнопкой `Stop LLM Test`.
307
+
308
+ ## Логи
309
+
310
+ ### UI logs
311
+
312
+ Правая панель показывает служебные события, ошибки, проверки evaluator и пути к файлам логов.
313
+
314
+ При `Auto LLM answer` и `LLM Test` обычные реплики не дублируются в правый служебный log, потому что они уже видны в transcript.
315
+
316
+ ### Write logs
317
+
318
+ Галочка `Write logs` пишет простой текстовый transcript:
319
+
320
+ ```text
321
+ ASSISTANT: ...
322
+
323
+ USER: ...
324
+ ```
325
+
326
+ Файлы создаются в:
327
+
328
+ ```text
329
+ transcript_logs/<workflow-name>/
330
+ ```
331
+
332
+ Эта папка не коммитится.
333
+
334
+ ### Test logs
335
+
336
+ `LLM Test` пишет подробные JSONL trace-файлы:
337
+
338
+ ```text
339
+ test_logs/<workflow-name>/
340
+ ```
341
+
342
+ Если evaluator считает вопрос или ответ плохим, первая строка файла будет `evaluation_failure_summary` с причиной.
343
+
344
+ ## Скриншоты
345
+
346
+ Скриншоты UI лучше хранить отдельно от runtime-логов, например:
347
+
348
+ ```text
349
+ scenarios/screenshots/
350
+ ```
351
+
352
+ Рекомендуемые скрины для README:
353
+
354
+ - общий вид canvas с RosUpack workflow;
355
+ - нижняя interactive panel во время прогона;
356
+ - пример `Knowledge Answer` ноды с привязанным файлом;
357
+ - пример `Semantic Branch` с альтернативами через `;`.
358
+
359
+ ## Git hygiene
360
+
361
+ В репозиторий должны попадать:
362
+
363
+ - `src/`
364
+ - `backend/main.py`
365
+ - `backend/requirements.txt`
366
+ - `public/`
367
+ - `workflows/`
368
+ - `scenarios/`
369
+ - конфиги приложения
370
+
371
+ В репозиторий не должны попадать:
372
+
373
+ - `node_modules/`
374
+ - `dist/`
375
+ - `backend/.venv/`
376
+ - `knowledge_contexts/`
377
+ - `test_logs/`
378
+ - `transcript_logs/`
379
+
380
+ ## GitHub
381
+
382
+ Repository:
383
+
384
+ ```text
385
+ https://github.com/markitzeroo/nodes-ui-flow
386
+ ```
387
+
388
+ Repository is private.
backend/.venv/Lib/site-packages/_distutils_hack/__init__.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # don't import any costly modules
2
+ import sys
3
+ import os
4
+
5
+
6
+ is_pypy = '__pypy__' in sys.builtin_module_names
7
+
8
+
9
+ def warn_distutils_present():
10
+ if 'distutils' not in sys.modules:
11
+ return
12
+ if is_pypy and sys.version_info < (3, 7):
13
+ # PyPy for 3.6 unconditionally imports distutils, so bypass the warning
14
+ # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
15
+ return
16
+ import warnings
17
+
18
+ warnings.warn(
19
+ "Distutils was imported before Setuptools, but importing Setuptools "
20
+ "also replaces the `distutils` module in `sys.modules`. This may lead "
21
+ "to undesirable behaviors or errors. To avoid these issues, avoid "
22
+ "using distutils directly, ensure that setuptools is installed in the "
23
+ "traditional way (e.g. not an editable install), and/or make sure "
24
+ "that setuptools is always imported before distutils."
25
+ )
26
+
27
+
28
+ def clear_distutils():
29
+ if 'distutils' not in sys.modules:
30
+ return
31
+ import warnings
32
+
33
+ warnings.warn("Setuptools is replacing distutils.")
34
+ mods = [
35
+ name
36
+ for name in sys.modules
37
+ if name == "distutils" or name.startswith("distutils.")
38
+ ]
39
+ for name in mods:
40
+ del sys.modules[name]
41
+
42
+
43
+ def enabled():
44
+ """
45
+ Allow selection of distutils by environment variable.
46
+ """
47
+ which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
48
+ return which == 'local'
49
+
50
+
51
+ def ensure_local_distutils():
52
+ import importlib
53
+
54
+ clear_distutils()
55
+
56
+ # With the DistutilsMetaFinder in place,
57
+ # perform an import to cause distutils to be
58
+ # loaded from setuptools._distutils. Ref #2906.
59
+ with shim():
60
+ importlib.import_module('distutils')
61
+
62
+ # check that submodules load as expected
63
+ core = importlib.import_module('distutils.core')
64
+ assert '_distutils' in core.__file__, core.__file__
65
+ assert 'setuptools._distutils.log' not in sys.modules
66
+
67
+
68
+ def do_override():
69
+ """
70
+ Ensure that the local copy of distutils is preferred over stdlib.
71
+
72
+ See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
73
+ for more motivation.
74
+ """
75
+ if enabled():
76
+ warn_distutils_present()
77
+ ensure_local_distutils()
78
+
79
+
80
+ class _TrivialRe:
81
+ def __init__(self, *patterns):
82
+ self._patterns = patterns
83
+
84
+ def match(self, string):
85
+ return all(pat in string for pat in self._patterns)
86
+
87
+
88
+ class DistutilsMetaFinder:
89
+ def find_spec(self, fullname, path, target=None):
90
+ # optimization: only consider top level modules and those
91
+ # found in the CPython test suite.
92
+ if path is not None and not fullname.startswith('test.'):
93
+ return
94
+
95
+ method_name = 'spec_for_{fullname}'.format(**locals())
96
+ method = getattr(self, method_name, lambda: None)
97
+ return method()
98
+
99
+ def spec_for_distutils(self):
100
+ if self.is_cpython():
101
+ return
102
+
103
+ import importlib
104
+ import importlib.abc
105
+ import importlib.util
106
+
107
+ try:
108
+ mod = importlib.import_module('setuptools._distutils')
109
+ except Exception:
110
+ # There are a couple of cases where setuptools._distutils
111
+ # may not be present:
112
+ # - An older Setuptools without a local distutils is
113
+ # taking precedence. Ref #2957.
114
+ # - Path manipulation during sitecustomize removes
115
+ # setuptools from the path but only after the hook
116
+ # has been loaded. Ref #2980.
117
+ # In either case, fall back to stdlib behavior.
118
+ return
119
+
120
+ class DistutilsLoader(importlib.abc.Loader):
121
+ def create_module(self, spec):
122
+ mod.__name__ = 'distutils'
123
+ return mod
124
+
125
+ def exec_module(self, module):
126
+ pass
127
+
128
+ return importlib.util.spec_from_loader(
129
+ 'distutils', DistutilsLoader(), origin=mod.__file__
130
+ )
131
+
132
+ @staticmethod
133
+ def is_cpython():
134
+ """
135
+ Suppress supplying distutils for CPython (build and tests).
136
+ Ref #2965 and #3007.
137
+ """
138
+ return os.path.isfile('pybuilddir.txt')
139
+
140
+ def spec_for_pip(self):
141
+ """
142
+ Ensure stdlib distutils when running under pip.
143
+ See pypa/pip#8761 for rationale.
144
+ """
145
+ if self.pip_imported_during_build():
146
+ return
147
+ clear_distutils()
148
+ self.spec_for_distutils = lambda: None
149
+
150
+ @classmethod
151
+ def pip_imported_during_build(cls):
152
+ """
153
+ Detect if pip is being imported in a build script. Ref #2355.
154
+ """
155
+ import traceback
156
+
157
+ return any(
158
+ cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
159
+ )
160
+
161
+ @staticmethod
162
+ def frame_file_is_setup(frame):
163
+ """
164
+ Return True if the indicated frame suggests a setup.py file.
165
+ """
166
+ # some frames may not have __file__ (#2940)
167
+ return frame.f_globals.get('__file__', '').endswith('setup.py')
168
+
169
+ def spec_for_sensitive_tests(self):
170
+ """
171
+ Ensure stdlib distutils when running select tests under CPython.
172
+
173
+ python/cpython#91169
174
+ """
175
+ clear_distutils()
176
+ self.spec_for_distutils = lambda: None
177
+
178
+ sensitive_tests = (
179
+ [
180
+ 'test.test_distutils',
181
+ 'test.test_peg_generator',
182
+ 'test.test_importlib',
183
+ ]
184
+ if sys.version_info < (3, 10)
185
+ else [
186
+ 'test.test_distutils',
187
+ ]
188
+ )
189
+
190
+
191
+ for name in DistutilsMetaFinder.sensitive_tests:
192
+ setattr(
193
+ DistutilsMetaFinder,
194
+ f'spec_for_{name}',
195
+ DistutilsMetaFinder.spec_for_sensitive_tests,
196
+ )
197
+
198
+
199
+ DISTUTILS_FINDER = DistutilsMetaFinder()
200
+
201
+
202
+ def add_shim():
203
+ DISTUTILS_FINDER in sys.meta_path or insert_shim()
204
+
205
+
206
+ class shim:
207
+ def __enter__(self):
208
+ insert_shim()
209
+
210
+ def __exit__(self, exc, value, tb):
211
+ remove_shim()
212
+
213
+
214
+ def insert_shim():
215
+ sys.meta_path.insert(0, DISTUTILS_FINDER)
216
+
217
+
218
+ def remove_shim():
219
+ try:
220
+ sys.meta_path.remove(DISTUTILS_FINDER)
221
+ except ValueError:
222
+ pass
backend/.venv/Lib/site-packages/_distutils_hack/override.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __import__('_distutils_hack').do_override()
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/METADATA ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: annotated-doc
3
+ Version: 0.0.4
4
+ Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
5
+ Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Intended Audience :: Information Technology
9
+ Classifier: Intended Audience :: System Administrators
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Topic :: Internet
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
19
+ Classifier: Development Status :: 4 - Beta
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.8
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Programming Language :: Python :: 3.14
29
+ Project-URL: Homepage, https://github.com/fastapi/annotated-doc
30
+ Project-URL: Documentation, https://github.com/fastapi/annotated-doc
31
+ Project-URL: Repository, https://github.com/fastapi/annotated-doc
32
+ Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
33
+ Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
34
+ Requires-Python: >=3.8
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Annotated Doc
38
+
39
+ Document parameters, class attributes, return types, and variables inline, with `Annotated`.
40
+
41
+ <a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
42
+ <img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
43
+ </a>
44
+ <a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
45
+ <img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
46
+ </a>
47
+ <a href="https://pypi.org/project/annotated-doc" target="_blank">
48
+ <img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
49
+ </a>
50
+ <a href="https://pypi.org/project/annotated-doc" target="_blank">
51
+ <img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
52
+ </a>
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install annotated-doc
58
+ ```
59
+
60
+ Or with `uv`:
61
+
62
+ ```Python
63
+ uv add annotated-doc
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
69
+
70
+ For example, to document a parameter `name` in a function `hi` you could do:
71
+
72
+ ```Python
73
+ from typing import Annotated
74
+
75
+ from annotated_doc import Doc
76
+
77
+ def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
78
+ print(f"Hi, {name}!")
79
+ ```
80
+
81
+ You can also use it to document class attributes:
82
+
83
+ ```Python
84
+ from typing import Annotated
85
+
86
+ from annotated_doc import Doc
87
+
88
+ class User:
89
+ name: Annotated[str, Doc("The user's name")]
90
+ age: Annotated[int, Doc("The user's age")]
91
+ ```
92
+
93
+ The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
94
+
95
+ ## Who Uses This
96
+
97
+ `annotated-doc` was made for:
98
+
99
+ * [FastAPI](https://fastapi.tiangolo.com/)
100
+ * [Typer](https://typer.tiangolo.com/)
101
+ * [SQLModel](https://sqlmodel.tiangolo.com/)
102
+ * [Asyncer](https://asyncer.tiangolo.com/)
103
+
104
+ `annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
105
+
106
+ ## Reasons not to use `annotated-doc`
107
+
108
+ You are already comfortable with one of the existing docstring formats, like:
109
+
110
+ * Sphinx
111
+ * numpydoc
112
+ * Google
113
+ * Keras
114
+
115
+ Your team is already comfortable using them.
116
+
117
+ You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
118
+
119
+ You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
120
+
121
+ ## Reasons to use `annotated-doc`
122
+
123
+ * No micro-syntax to learn for newcomers, it’s **just Python** syntax.
124
+ * **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
125
+ * **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
126
+ * **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
127
+ * **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
128
+ * **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
129
+ * **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
130
+ * **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
131
+ * A more formalized way to document other symbols, like type aliases, that could use Annotated.
132
+ * **Support** for apps using FastAPI, Typer and others.
133
+ * **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
134
+
135
+ ## History
136
+
137
+ I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
138
+
139
+ The conclusion was that this was better done as an external effort, in a third-party library.
140
+
141
+ So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
142
+
143
+ ## License
144
+
145
+ This project is licensed under the terms of the MIT license.
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/RECORD ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
3
+ annotated_doc-0.0.4.dist-info/RECORD,,
4
+ annotated_doc-0.0.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
6
+ annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
7
+ annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
8
+ annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
9
+ annotated_doc/__pycache__/__init__.cpython-310.pyc,,
10
+ annotated_doc/__pycache__/main.cpython-310.pyc,,
11
+ annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
12
+ annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/REQUESTED ADDED
File without changes
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.5)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+
backend/.venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Sebastián Ramírez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
backend/.venv/Lib/site-packages/annotated_doc/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .main import Doc as Doc
2
+
3
+ __version__ = "0.0.4"
backend/.venv/Lib/site-packages/annotated_doc/main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Doc:
2
+ """Define the documentation of a type annotation using `Annotated`, to be
3
+ used in class attributes, function and method parameters, return values,
4
+ and variables.
5
+
6
+ The value should be a positional-only string literal to allow static tools
7
+ like editors and documentation generators to use it.
8
+
9
+ This complements docstrings.
10
+
11
+ The string value passed is available in the attribute `documentation`.
12
+
13
+ Example:
14
+
15
+ ```Python
16
+ from typing import Annotated
17
+ from annotated_doc import Doc
18
+
19
+ def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
20
+ print(f"Hi, {name}!")
21
+ ```
22
+ """
23
+
24
+ def __init__(self, documentation: str, /) -> None:
25
+ self.documentation = documentation
26
+
27
+ def __repr__(self) -> str:
28
+ return f"Doc({self.documentation!r})"
29
+
30
+ def __hash__(self) -> int:
31
+ return hash(self.documentation)
32
+
33
+ def __eq__(self, other: object) -> bool:
34
+ if not isinstance(other, Doc):
35
+ return NotImplemented
36
+ return self.documentation == other.documentation
backend/.venv/Lib/site-packages/annotated_doc/py.typed ADDED
File without changes
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.3
2
+ Name: annotated-types
3
+ Version: 0.7.0
4
+ Summary: Reusable constraint types to use with typing.Annotated
5
+ Project-URL: Homepage, https://github.com/annotated-types/annotated-types
6
+ Project-URL: Source, https://github.com/annotated-types/annotated-types
7
+ Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
8
+ Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Environment :: MacOS X
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Operating System :: Unix
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.8
27
+ Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # annotated-types
31
+
32
+ [![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
33
+ [![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types)
34
+ [![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types)
35
+ [![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
36
+
37
+ [PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
38
+ adding context-specific metadata to existing types, and specifies that
39
+ `Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
40
+ logic for `x`.
41
+
42
+ This package provides metadata objects which can be used to represent common
43
+ constraints such as upper and lower bounds on scalar values and collection sizes,
44
+ a `Predicate` marker for runtime checks, and
45
+ descriptions of how we intend these metadata to be interpreted. In some cases,
46
+ we also note alternative representations which do not require this package.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install annotated-types
52
+ ```
53
+
54
+ ## Examples
55
+
56
+ ```python
57
+ from typing import Annotated
58
+ from annotated_types import Gt, Len, Predicate
59
+
60
+ class MyClass:
61
+ age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
62
+ # Invalid: 17, 18, "19", 19.0, ...
63
+ factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
64
+ # Invalid: 4, 8, -2, 5.0, "prime", ...
65
+
66
+ my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
67
+ # Invalid: (1, 2), ["abc"], [0] * 20
68
+ ```
69
+
70
+ ## Documentation
71
+
72
+ _While `annotated-types` avoids runtime checks for performance, users should not
73
+ construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
74
+ Downstream implementors may choose to raise an error, emit a warning, silently ignore
75
+ a metadata item, etc., if the metadata objects described below are used with an
76
+ incompatible type - or for any other reason!_
77
+
78
+ ### Gt, Ge, Lt, Le
79
+
80
+ Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
81
+ dates, times, strings, sets, etc. Note that the boundary value need not be of the
82
+ same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
83
+ is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
84
+
85
+ We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
86
+ as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
87
+ the `annotated-types` package.
88
+
89
+ To be explicit, these types have the following meanings:
90
+
91
+ * `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
92
+ * `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
93
+ * `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
94
+ * `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
95
+
96
+ ### Interval
97
+
98
+ `Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
99
+ metadata object. `None` attributes should be ignored, and non-`None` attributes
100
+ treated as per the single bounds above.
101
+
102
+ ### MultipleOf
103
+
104
+ `MultipleOf(multiple_of=x)` might be interpreted in two ways:
105
+
106
+ 1. Python semantics, implying `value % multiple_of == 0`, or
107
+ 2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
108
+ where `int(value / multiple_of) == value / multiple_of`.
109
+
110
+ We encourage users to be aware of these two common interpretations and their
111
+ distinct behaviours, especially since very large or non-integer numbers make
112
+ it easy to cause silent data corruption due to floating-point imprecision.
113
+
114
+ We encourage libraries to carefully document which interpretation they implement.
115
+
116
+ ### MinLen, MaxLen, Len
117
+
118
+ `Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
119
+
120
+ As well as `Len()` which can optionally include upper and lower bounds, we also
121
+ provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
122
+ and `Len(max_length=y)` respectively.
123
+
124
+ `Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
125
+
126
+ Examples of usage:
127
+
128
+ * `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
129
+ * `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
130
+ * `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
131
+ * `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
132
+ * `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
133
+
134
+ #### Changed in v0.4.0
135
+
136
+ * `min_inclusive` has been renamed to `min_length`, no change in meaning
137
+ * `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
138
+ * The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
139
+ meaning of the upper bound in slices vs. `Len`
140
+
141
+ See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
142
+
143
+ ### Timezone
144
+
145
+ `Timezone` can be used with a `datetime` or a `time` to express which timezones
146
+ are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
147
+ `Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
148
+ expresses that any timezone-aware datetime is allowed. You may also pass a specific
149
+ timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
150
+ object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
151
+ allow a specific timezone, though we note that this is often a symptom of fragile design.
152
+
153
+ #### Changed in v0.x.x
154
+
155
+ * `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
156
+ `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
157
+
158
+ ### Unit
159
+
160
+ `Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
161
+ a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
162
+ would be a float representing a velocity in meters per second.
163
+
164
+ Please note that `annotated_types` itself makes no attempt to parse or validate
165
+ the unit string in any way. That is left entirely to downstream libraries,
166
+ such as [`pint`](https://pint.readthedocs.io) or
167
+ [`astropy.units`](https://docs.astropy.org/en/stable/units/).
168
+
169
+ An example of how a library might use this metadata:
170
+
171
+ ```python
172
+ from annotated_types import Unit
173
+ from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
174
+
175
+ # given a type annotated with a unit:
176
+ Meters = Annotated[float, Unit("m")]
177
+
178
+
179
+ # you can cast the annotation to a specific unit type with any
180
+ # callable that accepts a string and returns the desired type
181
+ T = TypeVar("T")
182
+ def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
183
+ if get_origin(tp) is Annotated:
184
+ for arg in get_args(tp):
185
+ if isinstance(arg, Unit):
186
+ return unit_cls(arg.unit)
187
+ return None
188
+
189
+
190
+ # using `pint`
191
+ import pint
192
+ pint_unit = cast_unit(Meters, pint.Unit)
193
+
194
+
195
+ # using `astropy.units`
196
+ import astropy.units as u
197
+ astropy_unit = cast_unit(Meters, u.Unit)
198
+ ```
199
+
200
+ ### Predicate
201
+
202
+ `Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
203
+ Users should prefer the statically inspectable metadata above, but if you need
204
+ the full power and flexibility of arbitrary runtime predicates... here it is.
205
+
206
+ For some common constraints, we provide generic types:
207
+
208
+ * `IsLower = Annotated[T, Predicate(str.islower)]`
209
+ * `IsUpper = Annotated[T, Predicate(str.isupper)]`
210
+ * `IsDigit = Annotated[T, Predicate(str.isdigit)]`
211
+ * `IsFinite = Annotated[T, Predicate(math.isfinite)]`
212
+ * `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
213
+ * `IsNan = Annotated[T, Predicate(math.isnan)]`
214
+ * `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
215
+ * `IsInfinite = Annotated[T, Predicate(math.isinf)]`
216
+ * `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
217
+
218
+ so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
219
+ (but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
220
+
221
+ Some libraries might have special logic to handle known or understandable predicates,
222
+ for example by checking for `str.isdigit` and using its presence to both call custom
223
+ logic to enforce digit-only strings, and customise some generated external schema.
224
+ Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
225
+ favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
226
+
227
+ To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
228
+
229
+ We do not specify what behaviour should be expected for predicates that raise
230
+ an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
231
+ skip invalid constraints, or statically raise an error; or it might try calling it
232
+ and then propagate or discard the resulting
233
+ `TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
234
+ exception. We encourage libraries to document the behaviour they choose.
235
+
236
+ ### Doc
237
+
238
+ `doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
239
+
240
+ It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
241
+
242
+ It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
243
+
244
+ This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
245
+
246
+ ### Integrating downstream types with `GroupedMetadata`
247
+
248
+ Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
249
+ This can help reduce verbosity and cognitive overhead for users.
250
+ For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
251
+
252
+ ```python
253
+ from dataclasses import dataclass
254
+ from typing import Iterator
255
+ from annotated_types import GroupedMetadata, Ge
256
+
257
+ @dataclass
258
+ class Field(GroupedMetadata):
259
+ ge: int | None = None
260
+ description: str | None = None
261
+
262
+ def __iter__(self) -> Iterator[object]:
263
+ # Iterating over a GroupedMetadata object should yield annotated-types
264
+ # constraint metadata objects which describe it as fully as possible,
265
+ # and may include other unknown objects too.
266
+ if self.ge is not None:
267
+ yield Ge(self.ge)
268
+ if self.description is not None:
269
+ yield Description(self.description)
270
+ ```
271
+
272
+ Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
273
+
274
+ Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
275
+
276
+ Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
277
+
278
+ ### Consuming metadata
279
+
280
+ We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
281
+
282
+ It is up to the implementer to determine how this metadata is used.
283
+ You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
284
+
285
+ ## Design & History
286
+
287
+ This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
288
+ and Hypothesis, with the goal of making it as easy as possible for end-users to
289
+ provide more informative annotations for use by runtime libraries.
290
+
291
+ It is deliberately minimal, and following PEP-593 allows considerable downstream
292
+ discretion in what (if anything!) they choose to support. Nonetheless, we expect
293
+ that staying simple and covering _only_ the most common use-cases will give users
294
+ and maintainers the best experience we can. If you'd like more constraints for your
295
+ types - follow our lead, by defining them and documenting them downstream!
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
3
+ annotated_types-0.7.0.dist-info/RECORD,,
4
+ annotated_types-0.7.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
6
+ annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
7
+ annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
8
+ annotated_types/__pycache__/__init__.cpython-310.pyc,,
9
+ annotated_types/__pycache__/test_cases.cpython-310.pyc,,
10
+ annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/REQUESTED ADDED
File without changes
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.24.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
backend/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 the contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
backend/.venv/Lib/site-packages/annotated_types/__init__.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import sys
3
+ import types
4
+ from dataclasses import dataclass
5
+ from datetime import tzinfo
6
+ from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
7
+
8
+ if sys.version_info < (3, 8):
9
+ from typing_extensions import Protocol, runtime_checkable
10
+ else:
11
+ from typing import Protocol, runtime_checkable
12
+
13
+ if sys.version_info < (3, 9):
14
+ from typing_extensions import Annotated, Literal
15
+ else:
16
+ from typing import Annotated, Literal
17
+
18
+ if sys.version_info < (3, 10):
19
+ EllipsisType = type(Ellipsis)
20
+ KW_ONLY = {}
21
+ SLOTS = {}
22
+ else:
23
+ from types import EllipsisType
24
+
25
+ KW_ONLY = {"kw_only": True}
26
+ SLOTS = {"slots": True}
27
+
28
+
29
+ __all__ = (
30
+ 'BaseMetadata',
31
+ 'GroupedMetadata',
32
+ 'Gt',
33
+ 'Ge',
34
+ 'Lt',
35
+ 'Le',
36
+ 'Interval',
37
+ 'MultipleOf',
38
+ 'MinLen',
39
+ 'MaxLen',
40
+ 'Len',
41
+ 'Timezone',
42
+ 'Predicate',
43
+ 'LowerCase',
44
+ 'UpperCase',
45
+ 'IsDigits',
46
+ 'IsFinite',
47
+ 'IsNotFinite',
48
+ 'IsNan',
49
+ 'IsNotNan',
50
+ 'IsInfinite',
51
+ 'IsNotInfinite',
52
+ 'doc',
53
+ 'DocInfo',
54
+ '__version__',
55
+ )
56
+
57
+ __version__ = '0.7.0'
58
+
59
+
60
+ T = TypeVar('T')
61
+
62
+
63
+ # arguments that start with __ are considered
64
+ # positional only
65
+ # see https://peps.python.org/pep-0484/#positional-only-arguments
66
+
67
+
68
+ class SupportsGt(Protocol):
69
+ def __gt__(self: T, __other: T) -> bool:
70
+ ...
71
+
72
+
73
+ class SupportsGe(Protocol):
74
+ def __ge__(self: T, __other: T) -> bool:
75
+ ...
76
+
77
+
78
+ class SupportsLt(Protocol):
79
+ def __lt__(self: T, __other: T) -> bool:
80
+ ...
81
+
82
+
83
+ class SupportsLe(Protocol):
84
+ def __le__(self: T, __other: T) -> bool:
85
+ ...
86
+
87
+
88
+ class SupportsMod(Protocol):
89
+ def __mod__(self: T, __other: T) -> T:
90
+ ...
91
+
92
+
93
+ class SupportsDiv(Protocol):
94
+ def __div__(self: T, __other: T) -> T:
95
+ ...
96
+
97
+
98
+ class BaseMetadata:
99
+ """Base class for all metadata.
100
+
101
+ This exists mainly so that implementers
102
+ can do `isinstance(..., BaseMetadata)` while traversing field annotations.
103
+ """
104
+
105
+ __slots__ = ()
106
+
107
+
108
+ @dataclass(frozen=True, **SLOTS)
109
+ class Gt(BaseMetadata):
110
+ """Gt(gt=x) implies that the value must be greater than x.
111
+
112
+ It can be used with any type that supports the ``>`` operator,
113
+ including numbers, dates and times, strings, sets, and so on.
114
+ """
115
+
116
+ gt: SupportsGt
117
+
118
+
119
+ @dataclass(frozen=True, **SLOTS)
120
+ class Ge(BaseMetadata):
121
+ """Ge(ge=x) implies that the value must be greater than or equal to x.
122
+
123
+ It can be used with any type that supports the ``>=`` operator,
124
+ including numbers, dates and times, strings, sets, and so on.
125
+ """
126
+
127
+ ge: SupportsGe
128
+
129
+
130
+ @dataclass(frozen=True, **SLOTS)
131
+ class Lt(BaseMetadata):
132
+ """Lt(lt=x) implies that the value must be less than x.
133
+
134
+ It can be used with any type that supports the ``<`` operator,
135
+ including numbers, dates and times, strings, sets, and so on.
136
+ """
137
+
138
+ lt: SupportsLt
139
+
140
+
141
+ @dataclass(frozen=True, **SLOTS)
142
+ class Le(BaseMetadata):
143
+ """Le(le=x) implies that the value must be less than or equal to x.
144
+
145
+ It can be used with any type that supports the ``<=`` operator,
146
+ including numbers, dates and times, strings, sets, and so on.
147
+ """
148
+
149
+ le: SupportsLe
150
+
151
+
152
+ @runtime_checkable
153
+ class GroupedMetadata(Protocol):
154
+ """A grouping of multiple objects, like typing.Unpack.
155
+
156
+ `GroupedMetadata` on its own is not metadata and has no meaning.
157
+ All of the constraints and metadata should be fully expressable
158
+ in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
159
+
160
+ Concrete implementations should override `GroupedMetadata.__iter__()`
161
+ to add their own metadata.
162
+ For example:
163
+
164
+ >>> @dataclass
165
+ >>> class Field(GroupedMetadata):
166
+ >>> gt: float | None = None
167
+ >>> description: str | None = None
168
+ ...
169
+ >>> def __iter__(self) -> Iterable[object]:
170
+ >>> if self.gt is not None:
171
+ >>> yield Gt(self.gt)
172
+ >>> if self.description is not None:
173
+ >>> yield Description(self.gt)
174
+
175
+ Also see the implementation of `Interval` below for an example.
176
+
177
+ Parsers should recognize this and unpack it so that it can be used
178
+ both with and without unpacking:
179
+
180
+ - `Annotated[int, Field(...)]` (parser must unpack Field)
181
+ - `Annotated[int, *Field(...)]` (PEP-646)
182
+ """ # noqa: trailing-whitespace
183
+
184
+ @property
185
+ def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
186
+ return True
187
+
188
+ def __iter__(self) -> Iterator[object]:
189
+ ...
190
+
191
+ if not TYPE_CHECKING:
192
+ __slots__ = () # allow subclasses to use slots
193
+
194
+ def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
195
+ # Basic ABC like functionality without the complexity of an ABC
196
+ super().__init_subclass__(*args, **kwargs)
197
+ if cls.__iter__ is GroupedMetadata.__iter__:
198
+ raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
199
+
200
+ def __iter__(self) -> Iterator[object]: # noqa: F811
201
+ raise NotImplementedError # more helpful than "None has no attribute..." type errors
202
+
203
+
204
+ @dataclass(frozen=True, **KW_ONLY, **SLOTS)
205
+ class Interval(GroupedMetadata):
206
+ """Interval can express inclusive or exclusive bounds with a single object.
207
+
208
+ It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
209
+ are interpreted the same way as the single-bound constraints.
210
+ """
211
+
212
+ gt: Union[SupportsGt, None] = None
213
+ ge: Union[SupportsGe, None] = None
214
+ lt: Union[SupportsLt, None] = None
215
+ le: Union[SupportsLe, None] = None
216
+
217
+ def __iter__(self) -> Iterator[BaseMetadata]:
218
+ """Unpack an Interval into zero or more single-bounds."""
219
+ if self.gt is not None:
220
+ yield Gt(self.gt)
221
+ if self.ge is not None:
222
+ yield Ge(self.ge)
223
+ if self.lt is not None:
224
+ yield Lt(self.lt)
225
+ if self.le is not None:
226
+ yield Le(self.le)
227
+
228
+
229
+ @dataclass(frozen=True, **SLOTS)
230
+ class MultipleOf(BaseMetadata):
231
+ """MultipleOf(multiple_of=x) might be interpreted in two ways:
232
+
233
+ 1. Python semantics, implying ``value % multiple_of == 0``, or
234
+ 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
235
+
236
+ We encourage users to be aware of these two common interpretations,
237
+ and libraries to carefully document which they implement.
238
+ """
239
+
240
+ multiple_of: Union[SupportsDiv, SupportsMod]
241
+
242
+
243
+ @dataclass(frozen=True, **SLOTS)
244
+ class MinLen(BaseMetadata):
245
+ """
246
+ MinLen() implies minimum inclusive length,
247
+ e.g. ``len(value) >= min_length``.
248
+ """
249
+
250
+ min_length: Annotated[int, Ge(0)]
251
+
252
+
253
+ @dataclass(frozen=True, **SLOTS)
254
+ class MaxLen(BaseMetadata):
255
+ """
256
+ MaxLen() implies maximum inclusive length,
257
+ e.g. ``len(value) <= max_length``.
258
+ """
259
+
260
+ max_length: Annotated[int, Ge(0)]
261
+
262
+
263
+ @dataclass(frozen=True, **SLOTS)
264
+ class Len(GroupedMetadata):
265
+ """
266
+ Len() implies that ``min_length <= len(value) <= max_length``.
267
+
268
+ Upper bound may be omitted or ``None`` to indicate no upper length bound.
269
+ """
270
+
271
+ min_length: Annotated[int, Ge(0)] = 0
272
+ max_length: Optional[Annotated[int, Ge(0)]] = None
273
+
274
+ def __iter__(self) -> Iterator[BaseMetadata]:
275
+ """Unpack a Len into zone or more single-bounds."""
276
+ if self.min_length > 0:
277
+ yield MinLen(self.min_length)
278
+ if self.max_length is not None:
279
+ yield MaxLen(self.max_length)
280
+
281
+
282
+ @dataclass(frozen=True, **SLOTS)
283
+ class Timezone(BaseMetadata):
284
+ """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
285
+
286
+ ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
287
+ ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
288
+ tz-aware but any timezone is allowed.
289
+
290
+ You may also pass a specific timezone string or tzinfo object such as
291
+ ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
292
+ you only allow a specific timezone, though we note that this is often
293
+ a symptom of poor design.
294
+ """
295
+
296
+ tz: Union[str, tzinfo, EllipsisType, None]
297
+
298
+
299
+ @dataclass(frozen=True, **SLOTS)
300
+ class Unit(BaseMetadata):
301
+ """Indicates that the value is a physical quantity with the specified unit.
302
+
303
+ It is intended for usage with numeric types, where the value represents the
304
+ magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
305
+ or ``speed: Annotated[float, Unit('m/s')]``.
306
+
307
+ Interpretation of the unit string is left to the discretion of the consumer.
308
+ It is suggested to follow conventions established by python libraries that work
309
+ with physical quantities, such as
310
+
311
+ - ``pint`` : <https://pint.readthedocs.io/en/stable/>
312
+ - ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
313
+
314
+ For indicating a quantity with a certain dimensionality but without a specific unit
315
+ it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
316
+ Note, however, ``annotated_types`` itself makes no use of the unit string.
317
+ """
318
+
319
+ unit: str
320
+
321
+
322
+ @dataclass(frozen=True, **SLOTS)
323
+ class Predicate(BaseMetadata):
324
+ """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
325
+
326
+ Users should prefer statically inspectable metadata, but if you need the full
327
+ power and flexibility of arbitrary runtime predicates... here it is.
328
+
329
+ We provide a few predefined predicates for common string constraints:
330
+ ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
331
+ ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
332
+ can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
333
+
334
+ Some libraries might have special logic to handle certain predicates, e.g. by
335
+ checking for `str.isdigit` and using its presence to both call custom logic to
336
+ enforce digit-only strings, and customise some generated external schema.
337
+
338
+ We do not specify what behaviour should be expected for predicates that raise
339
+ an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
340
+ skip invalid constraints, or statically raise an error; or it might try calling it
341
+ and then propagate or discard the resulting exception.
342
+ """
343
+
344
+ func: Callable[[Any], bool]
345
+
346
+ def __repr__(self) -> str:
347
+ if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
348
+ return f"{self.__class__.__name__}({self.func!r})"
349
+ if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
350
+ namespace := getattr(self.func.__self__, "__name__", None)
351
+ ):
352
+ return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
353
+ if isinstance(self.func, type(str.isascii)): # method descriptor
354
+ return f"{self.__class__.__name__}({self.func.__qualname__})"
355
+ return f"{self.__class__.__name__}({self.func.__name__})"
356
+
357
+
358
+ @dataclass
359
+ class Not:
360
+ func: Callable[[Any], bool]
361
+
362
+ def __call__(self, __v: Any) -> bool:
363
+ return not self.func(__v)
364
+
365
+
366
+ _StrType = TypeVar("_StrType", bound=str)
367
+
368
+ LowerCase = Annotated[_StrType, Predicate(str.islower)]
369
+ """
370
+ Return True if the string is a lowercase string, False otherwise.
371
+
372
+ A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
373
+ """ # noqa: E501
374
+ UpperCase = Annotated[_StrType, Predicate(str.isupper)]
375
+ """
376
+ Return True if the string is an uppercase string, False otherwise.
377
+
378
+ A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
379
+ """ # noqa: E501
380
+ IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
381
+ IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
382
+ """
383
+ Return True if the string is a digit string, False otherwise.
384
+
385
+ A string is a digit string if all characters in the string are digits and there is at least one character in the string.
386
+ """ # noqa: E501
387
+ IsAscii = Annotated[_StrType, Predicate(str.isascii)]
388
+ """
389
+ Return True if all characters in the string are ASCII, False otherwise.
390
+
391
+ ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
392
+ """
393
+
394
+ _NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
395
+ IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
396
+ """Return True if x is neither an infinity nor a NaN, and False otherwise."""
397
+ IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
398
+ """Return True if x is one of infinity or NaN, and False otherwise"""
399
+ IsNan = Annotated[_NumericType, Predicate(math.isnan)]
400
+ """Return True if x is a NaN (not a number), and False otherwise."""
401
+ IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
402
+ """Return True if x is anything but NaN (not a number), and False otherwise."""
403
+ IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
404
+ """Return True if x is a positive or negative infinity, and False otherwise."""
405
+ IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
406
+ """Return True if x is neither a positive or negative infinity, and False otherwise."""
407
+
408
+ try:
409
+ from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
410
+ except ImportError:
411
+
412
+ @dataclass(frozen=True, **SLOTS)
413
+ class DocInfo: # type: ignore [no-redef]
414
+ """ "
415
+ The return value of doc(), mainly to be used by tools that want to extract the
416
+ Annotated documentation at runtime.
417
+ """
418
+
419
+ documentation: str
420
+ """The documentation string passed to doc()."""
421
+
422
+ def doc(
423
+ documentation: str,
424
+ ) -> DocInfo:
425
+ """
426
+ Add documentation to a type annotation inside of Annotated.
427
+
428
+ For example:
429
+
430
+ >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
431
+ """
432
+ return DocInfo(documentation)
backend/.venv/Lib/site-packages/annotated_types/py.typed ADDED
File without changes
backend/.venv/Lib/site-packages/annotated_types/test_cases.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import sys
3
+ from datetime import date, datetime, timedelta, timezone
4
+ from decimal import Decimal
5
+ from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
6
+
7
+ if sys.version_info < (3, 9):
8
+ from typing_extensions import Annotated
9
+ else:
10
+ from typing import Annotated
11
+
12
+ import annotated_types as at
13
+
14
+
15
+ class Case(NamedTuple):
16
+ """
17
+ A test case for `annotated_types`.
18
+ """
19
+
20
+ annotation: Any
21
+ valid_cases: Iterable[Any]
22
+ invalid_cases: Iterable[Any]
23
+
24
+
25
+ def cases() -> Iterable[Case]:
26
+ # Gt, Ge, Lt, Le
27
+ yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
28
+ yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
29
+ yield Case(
30
+ Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
31
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
32
+ [datetime(2000, 1, 1), datetime(1999, 12, 31)],
33
+ )
34
+ yield Case(
35
+ Annotated[datetime, at.Gt(date(2000, 1, 1))],
36
+ [date(2000, 1, 2), date(2000, 1, 3)],
37
+ [date(2000, 1, 1), date(1999, 12, 31)],
38
+ )
39
+ yield Case(
40
+ Annotated[datetime, at.Gt(Decimal('1.123'))],
41
+ [Decimal('1.1231'), Decimal('123')],
42
+ [Decimal('1.123'), Decimal('0')],
43
+ )
44
+
45
+ yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
46
+ yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
47
+ yield Case(
48
+ Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
49
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
50
+ [datetime(1998, 1, 1), datetime(1999, 12, 31)],
51
+ )
52
+
53
+ yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
54
+ yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
55
+ yield Case(
56
+ Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
57
+ [datetime(1999, 12, 31), datetime(1999, 12, 31)],
58
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
59
+ )
60
+
61
+ yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
62
+ yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
63
+ yield Case(
64
+ Annotated[datetime, at.Le(datetime(2000, 1, 1))],
65
+ [datetime(2000, 1, 1), datetime(1999, 12, 31)],
66
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
67
+ )
68
+
69
+ # Interval
70
+ yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
71
+ yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
72
+ yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
73
+ yield Case(
74
+ Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
75
+ [datetime(2000, 1, 2), datetime(2000, 1, 3)],
76
+ [datetime(2000, 1, 1), datetime(2000, 1, 4)],
77
+ )
78
+
79
+ yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
80
+ yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
81
+
82
+ # lengths
83
+
84
+ yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
85
+ yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
86
+ yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
87
+ yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
88
+
89
+ yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
90
+ yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
91
+ yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
92
+ yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
93
+
94
+ yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
95
+ yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
96
+
97
+ yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
98
+ yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
99
+ yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
100
+
101
+ # Timezone
102
+
103
+ yield Case(
104
+ Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
105
+ )
106
+ yield Case(
107
+ Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
108
+ )
109
+ yield Case(
110
+ Annotated[datetime, at.Timezone(timezone.utc)],
111
+ [datetime(2000, 1, 1, tzinfo=timezone.utc)],
112
+ [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
113
+ )
114
+ yield Case(
115
+ Annotated[datetime, at.Timezone('Europe/London')],
116
+ [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
117
+ [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
118
+ )
119
+
120
+ # Quantity
121
+
122
+ yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
123
+
124
+ # predicate types
125
+
126
+ yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
127
+ yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
128
+ yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
129
+ yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
130
+
131
+ yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
132
+
133
+ yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
134
+ yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
135
+ yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
136
+ yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
137
+ yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
138
+ yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
139
+
140
+ # check stacked predicates
141
+ yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
142
+
143
+ # doc
144
+ yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
145
+
146
+ # custom GroupedMetadata
147
+ class MyCustomGroupedMetadata(at.GroupedMetadata):
148
+ def __iter__(self) -> Iterator[at.Predicate]:
149
+ yield at.Predicate(lambda x: float(x).is_integer())
150
+
151
+ yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/METADATA ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: anyio
3
+ Version: 4.12.0
4
+ Summary: High-level concurrency and networking framework on top of asyncio or Trio
5
+ Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
8
+ Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
9
+ Project-URL: Source code, https://github.com/agronholm/anyio
10
+ Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Framework :: AnyIO
14
+ Classifier: Typing :: Typed
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE
26
+ Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
27
+ Requires-Dist: idna>=2.8
28
+ Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
29
+ Provides-Extra: trio
30
+ Requires-Dist: trio>=0.32.0; python_version >= "3.10" and extra == "trio"
31
+ Requires-Dist: trio>=0.31.0; python_version < "3.10" and extra == "trio"
32
+ Dynamic: license-file
33
+
34
+ .. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
35
+ :target: https://github.com/agronholm/anyio/actions/workflows/test.yml
36
+ :alt: Build Status
37
+ .. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
38
+ :target: https://coveralls.io/github/agronholm/anyio?branch=master
39
+ :alt: Code Coverage
40
+ .. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
41
+ :target: https://anyio.readthedocs.io/en/latest/?badge=latest
42
+ :alt: Documentation
43
+ .. image:: https://badges.gitter.im/gitterHQ/gitter.svg
44
+ :target: https://gitter.im/python-trio/AnyIO
45
+ :alt: Gitter chat
46
+
47
+ AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
48
+ Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
49
+ with the native SC of Trio itself.
50
+
51
+ Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
52
+ Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
53
+ refactoring necessary. It will blend in with the native libraries of your chosen backend.
54
+
55
+ To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
56
+ `here <https://anyio.readthedocs.io/en/stable/why.html>`_.
57
+
58
+ Documentation
59
+ -------------
60
+
61
+ View full documentation at: https://anyio.readthedocs.io/
62
+
63
+ Features
64
+ --------
65
+
66
+ AnyIO offers the following functionality:
67
+
68
+ * Task groups (nurseries_ in trio terminology)
69
+ * High-level networking (TCP, UDP and UNIX sockets)
70
+
71
+ * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
72
+ 3.8)
73
+ * async/await style UDP sockets (unlike asyncio where you still have to use Transports and
74
+ Protocols)
75
+
76
+ * A versatile API for byte streams and object streams
77
+ * Inter-task synchronization and communication (locks, conditions, events, semaphores, object
78
+ streams)
79
+ * Worker threads
80
+ * Subprocesses
81
+ * Subinterpreter support for code parallelization (on Python 3.13 and later)
82
+ * Asynchronous file I/O (using worker threads)
83
+ * Signal handling
84
+ * Asynchronous version of the functools_ module
85
+
86
+ AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
87
+ It even works with the popular Hypothesis_ library.
88
+
89
+ .. _asyncio: https://docs.python.org/3/library/asyncio.html
90
+ .. _Trio: https://github.com/python-trio/trio
91
+ .. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
92
+ .. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
93
+ .. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
94
+ .. _pytest: https://docs.pytest.org/en/latest/
95
+ .. _functools: https://docs.python.org/3/library/functools.html
96
+ .. _Hypothesis: https://hypothesis.works/
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/RECORD ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio-4.12.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ anyio-4.12.0.dist-info/METADATA,sha256=rte2_C2hYKP9_iVMFYogSzBxdHBzwY45S1TrLiBsxdk,4277
3
+ anyio-4.12.0.dist-info/RECORD,,
4
+ anyio-4.12.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ anyio-4.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ anyio-4.12.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
7
+ anyio-4.12.0.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
8
+ anyio-4.12.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
9
+ anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170
10
+ anyio/__pycache__/__init__.cpython-310.pyc,,
11
+ anyio/__pycache__/from_thread.cpython-310.pyc,,
12
+ anyio/__pycache__/functools.cpython-310.pyc,,
13
+ anyio/__pycache__/lowlevel.cpython-310.pyc,,
14
+ anyio/__pycache__/pytest_plugin.cpython-310.pyc,,
15
+ anyio/__pycache__/to_interpreter.cpython-310.pyc,,
16
+ anyio/__pycache__/to_process.cpython-310.pyc,,
17
+ anyio/__pycache__/to_thread.cpython-310.pyc,,
18
+ anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ anyio/_backends/__pycache__/__init__.cpython-310.pyc,,
20
+ anyio/_backends/__pycache__/_asyncio.cpython-310.pyc,,
21
+ anyio/_backends/__pycache__/_trio.cpython-310.pyc,,
22
+ anyio/_backends/_asyncio.py,sha256=w6gCSMs_2D1doKVtzi32bOloBl1df-IHubl8-Vks908,99656
23
+ anyio/_backends/_trio.py,sha256=ScNVMQB0iiuJMAon1epQCVOVbIbf-Lxnfb5OxujzMok,42398
24
+ anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ anyio/_core/__pycache__/__init__.cpython-310.pyc,,
26
+ anyio/_core/__pycache__/_asyncio_selector_thread.cpython-310.pyc,,
27
+ anyio/_core/__pycache__/_contextmanagers.cpython-310.pyc,,
28
+ anyio/_core/__pycache__/_eventloop.cpython-310.pyc,,
29
+ anyio/_core/__pycache__/_exceptions.cpython-310.pyc,,
30
+ anyio/_core/__pycache__/_fileio.cpython-310.pyc,,
31
+ anyio/_core/__pycache__/_resources.cpython-310.pyc,,
32
+ anyio/_core/__pycache__/_signals.cpython-310.pyc,,
33
+ anyio/_core/__pycache__/_sockets.cpython-310.pyc,,
34
+ anyio/_core/__pycache__/_streams.cpython-310.pyc,,
35
+ anyio/_core/__pycache__/_subprocesses.cpython-310.pyc,,
36
+ anyio/_core/__pycache__/_synchronization.cpython-310.pyc,,
37
+ anyio/_core/__pycache__/_tasks.cpython-310.pyc,,
38
+ anyio/_core/__pycache__/_tempfile.cpython-310.pyc,,
39
+ anyio/_core/__pycache__/_testing.cpython-310.pyc,,
40
+ anyio/_core/__pycache__/_typedattr.cpython-310.pyc,,
41
+ anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
42
+ anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
43
+ anyio/_core/_eventloop.py,sha256=xsoYgHIddNYusTqAFDVmcvpjHKJFmdgtDcAjpN3JEWQ,6261
44
+ anyio/_core/_exceptions.py,sha256=fR2SvRUBYVHvolNKbzWSLt8FC_5NFB2OAzGD738fD8Q,4257
45
+ anyio/_core/_fileio.py,sha256=uc7t10Vb-If7GbdWM_zFf-ajUe6uek63fSt7IBLlZW0,25731
46
+ anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
47
+ anyio/_core/_signals.py,sha256=vulT1M1xdLYtAR-eY5TamIgaf1WTlOwOrMGwswlTTr8,905
48
+ anyio/_core/_sockets.py,sha256=aTbgMr0qPmBPfrapxLykyajsmS7IAerhW9_Qk5r5E18,34311
49
+ anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
50
+ anyio/_core/_subprocesses.py,sha256=EXm5igL7dj55iYkPlbYVAqtbqxJxjU-6OndSTIx9SRg,8047
51
+ anyio/_core/_synchronization.py,sha256=SY3nsr1ZZyDrjamsOVoYcvj-x6d_AR13Cu5lZecG0gY,20894
52
+ anyio/_core/_tasks.py,sha256=km6hVE1fsuIenya3MDud8KP6-J_bNzlgYC10wUxI7iA,4880
53
+ anyio/_core/_tempfile.py,sha256=lHb7CW4FyIlpkf5ADAf4VmLHCKwEHF9nxqNyBCFFUiA,19697
54
+ anyio/_core/_testing.py,sha256=YUGwA5cgFFbUTv4WFd7cv_BSVr4ryTtPp8owQA3JdWE,2118
55
+ anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
56
+ anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
57
+ anyio/abc/__pycache__/__init__.cpython-310.pyc,,
58
+ anyio/abc/__pycache__/_eventloop.cpython-310.pyc,,
59
+ anyio/abc/__pycache__/_resources.cpython-310.pyc,,
60
+ anyio/abc/__pycache__/_sockets.cpython-310.pyc,,
61
+ anyio/abc/__pycache__/_streams.cpython-310.pyc,,
62
+ anyio/abc/__pycache__/_subprocesses.cpython-310.pyc,,
63
+ anyio/abc/__pycache__/_tasks.cpython-310.pyc,,
64
+ anyio/abc/__pycache__/_testing.cpython-310.pyc,,
65
+ anyio/abc/_eventloop.py,sha256=GTZbdItBHcj_b-8K2XylET2-bBYLZ3XjW4snY7vK7LE,10900
66
+ anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
67
+ anyio/abc/_sockets.py,sha256=ECTY0jLEF18gryANHR3vFzXzGdZ-xPwELq1QdgOb0Jo,13258
68
+ anyio/abc/_streams.py,sha256=005GKSCXGprxnhucILboSqc2JFovECZk9m3p-qqxXVc,7640
69
+ anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
70
+ anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721
71
+ anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821
72
+ anyio/from_thread.py,sha256=-YZOTpu9WVHtAsMxQGIOaHMjaDRNeKQilx6Nn2qDU-o,19017
73
+ anyio/functools.py,sha256=tIWQ90cuLMxfJIpdBfFY3W3CC1zqFCRAyR3DxKc0Xlo,10061
74
+ anyio/lowlevel.py,sha256=NnPYQ6tWDzLRwpalX2CvsbkXkTeasbJcL52gPopWdYg,5048
75
+ anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
+ anyio/pytest_plugin.py,sha256=3jAFQn0jv_pyoWE2GBBlHaj9sqXj4e8vob0_hgrsXE8,10244
77
+ anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
+ anyio/streams/__pycache__/__init__.cpython-310.pyc,,
79
+ anyio/streams/__pycache__/buffered.cpython-310.pyc,,
80
+ anyio/streams/__pycache__/file.cpython-310.pyc,,
81
+ anyio/streams/__pycache__/memory.cpython-310.pyc,,
82
+ anyio/streams/__pycache__/stapled.cpython-310.pyc,,
83
+ anyio/streams/__pycache__/text.cpython-310.pyc,,
84
+ anyio/streams/__pycache__/tls.cpython-310.pyc,,
85
+ anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263
86
+ anyio/streams/file.py,sha256=4WZ7XGz5WNu39FQHvqbe__TQ0HDP9OOhgO1mk9iVpVU,4470
87
+ anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740
88
+ anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390
89
+ anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
90
+ anyio/streams/tls.py,sha256=Jpxy0Mfbcp1BxHCwE-YjSSFaLnIBbnnwur-excYThs4,15368
91
+ anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
92
+ anyio/to_process.py,sha256=cEyYUgb8LJVRJCfs6rK3aEM_T3k2gEmhl0nBjEvflOk,9687
93
+ anyio/to_thread.py,sha256=tXQPvHohvQ2Vrw2pBtdzkRPNV7u3H2_UDbvwL2u_R7k,2465
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/REQUESTED ADDED
File without changes
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [pytest11]
2
+ anyio = anyio.pytest_plugin
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Alex Grönholm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
backend/.venv/Lib/site-packages/anyio-4.12.0.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ anyio
backend/.venv/Lib/site-packages/anyio/__init__.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
4
+ from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
5
+ from ._core._eventloop import current_time as current_time
6
+ from ._core._eventloop import get_all_backends as get_all_backends
7
+ from ._core._eventloop import get_available_backends as get_available_backends
8
+ from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
9
+ from ._core._eventloop import run as run
10
+ from ._core._eventloop import sleep as sleep
11
+ from ._core._eventloop import sleep_forever as sleep_forever
12
+ from ._core._eventloop import sleep_until as sleep_until
13
+ from ._core._exceptions import BrokenResourceError as BrokenResourceError
14
+ from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
15
+ from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
16
+ from ._core._exceptions import BusyResourceError as BusyResourceError
17
+ from ._core._exceptions import ClosedResourceError as ClosedResourceError
18
+ from ._core._exceptions import ConnectionFailed as ConnectionFailed
19
+ from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
20
+ from ._core._exceptions import EndOfStream as EndOfStream
21
+ from ._core._exceptions import IncompleteRead as IncompleteRead
22
+ from ._core._exceptions import NoEventLoopError as NoEventLoopError
23
+ from ._core._exceptions import RunFinishedError as RunFinishedError
24
+ from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
25
+ from ._core._exceptions import WouldBlock as WouldBlock
26
+ from ._core._fileio import AsyncFile as AsyncFile
27
+ from ._core._fileio import Path as Path
28
+ from ._core._fileio import open_file as open_file
29
+ from ._core._fileio import wrap_file as wrap_file
30
+ from ._core._resources import aclose_forcefully as aclose_forcefully
31
+ from ._core._signals import open_signal_receiver as open_signal_receiver
32
+ from ._core._sockets import TCPConnectable as TCPConnectable
33
+ from ._core._sockets import UNIXConnectable as UNIXConnectable
34
+ from ._core._sockets import as_connectable as as_connectable
35
+ from ._core._sockets import connect_tcp as connect_tcp
36
+ from ._core._sockets import connect_unix as connect_unix
37
+ from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
38
+ from ._core._sockets import (
39
+ create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
40
+ )
41
+ from ._core._sockets import create_tcp_listener as create_tcp_listener
42
+ from ._core._sockets import create_udp_socket as create_udp_socket
43
+ from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
44
+ from ._core._sockets import create_unix_listener as create_unix_listener
45
+ from ._core._sockets import getaddrinfo as getaddrinfo
46
+ from ._core._sockets import getnameinfo as getnameinfo
47
+ from ._core._sockets import notify_closing as notify_closing
48
+ from ._core._sockets import wait_readable as wait_readable
49
+ from ._core._sockets import wait_socket_readable as wait_socket_readable
50
+ from ._core._sockets import wait_socket_writable as wait_socket_writable
51
+ from ._core._sockets import wait_writable as wait_writable
52
+ from ._core._streams import create_memory_object_stream as create_memory_object_stream
53
+ from ._core._subprocesses import open_process as open_process
54
+ from ._core._subprocesses import run_process as run_process
55
+ from ._core._synchronization import CapacityLimiter as CapacityLimiter
56
+ from ._core._synchronization import (
57
+ CapacityLimiterStatistics as CapacityLimiterStatistics,
58
+ )
59
+ from ._core._synchronization import Condition as Condition
60
+ from ._core._synchronization import ConditionStatistics as ConditionStatistics
61
+ from ._core._synchronization import Event as Event
62
+ from ._core._synchronization import EventStatistics as EventStatistics
63
+ from ._core._synchronization import Lock as Lock
64
+ from ._core._synchronization import LockStatistics as LockStatistics
65
+ from ._core._synchronization import ResourceGuard as ResourceGuard
66
+ from ._core._synchronization import Semaphore as Semaphore
67
+ from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
68
+ from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
69
+ from ._core._tasks import CancelScope as CancelScope
70
+ from ._core._tasks import create_task_group as create_task_group
71
+ from ._core._tasks import current_effective_deadline as current_effective_deadline
72
+ from ._core._tasks import fail_after as fail_after
73
+ from ._core._tasks import move_on_after as move_on_after
74
+ from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
75
+ from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
76
+ from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
77
+ from ._core._tempfile import TemporaryFile as TemporaryFile
78
+ from ._core._tempfile import gettempdir as gettempdir
79
+ from ._core._tempfile import gettempdirb as gettempdirb
80
+ from ._core._tempfile import mkdtemp as mkdtemp
81
+ from ._core._tempfile import mkstemp as mkstemp
82
+ from ._core._testing import TaskInfo as TaskInfo
83
+ from ._core._testing import get_current_task as get_current_task
84
+ from ._core._testing import get_running_tasks as get_running_tasks
85
+ from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
86
+ from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
87
+ from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
88
+ from ._core._typedattr import typed_attribute as typed_attribute
89
+
90
+ # Re-export imports so they look like they live directly in this package
91
+ for __value in list(locals().values()):
92
+ if getattr(__value, "__module__", "").startswith("anyio."):
93
+ __value.__module__ = __name__
94
+
95
+
96
+ del __value
97
+
98
+
99
+ def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
100
+ """Support deprecated aliases."""
101
+ if attr == "BrokenWorkerIntepreter":
102
+ import warnings
103
+
104
+ warnings.warn(
105
+ "The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
106
+ DeprecationWarning,
107
+ stacklevel=2,
108
+ )
109
+ return BrokenWorkerInterpreter
110
+
111
+ raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
backend/.venv/Lib/site-packages/anyio/_backends/__init__.py ADDED
File without changes
backend/.venv/Lib/site-packages/anyio/_backends/_asyncio.py ADDED
The diff for this file is too large to render. See raw diff
 
backend/.venv/Lib/site-packages/anyio/_backends/_trio.py ADDED
@@ -0,0 +1,1384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import array
4
+ import math
5
+ import os
6
+ import socket
7
+ import sys
8
+ import types
9
+ import weakref
10
+ from collections.abc import (
11
+ AsyncGenerator,
12
+ AsyncIterator,
13
+ Awaitable,
14
+ Callable,
15
+ Collection,
16
+ Coroutine,
17
+ Iterable,
18
+ Sequence,
19
+ )
20
+ from concurrent.futures import Future
21
+ from contextlib import AbstractContextManager
22
+ from dataclasses import dataclass
23
+ from functools import partial
24
+ from io import IOBase
25
+ from os import PathLike
26
+ from signal import Signals
27
+ from socket import AddressFamily, SocketKind
28
+ from types import TracebackType
29
+ from typing import (
30
+ IO,
31
+ TYPE_CHECKING,
32
+ Any,
33
+ Generic,
34
+ NoReturn,
35
+ TypeVar,
36
+ cast,
37
+ overload,
38
+ )
39
+
40
+ import trio.from_thread
41
+ import trio.lowlevel
42
+ from outcome import Error, Outcome, Value
43
+ from trio.lowlevel import (
44
+ current_root_task,
45
+ current_task,
46
+ notify_closing,
47
+ wait_readable,
48
+ wait_writable,
49
+ )
50
+ from trio.socket import SocketType as TrioSocketType
51
+ from trio.to_thread import run_sync
52
+
53
+ from .. import (
54
+ CapacityLimiterStatistics,
55
+ EventStatistics,
56
+ LockStatistics,
57
+ RunFinishedError,
58
+ TaskInfo,
59
+ WouldBlock,
60
+ abc,
61
+ )
62
+ from .._core._eventloop import claim_worker_thread
63
+ from .._core._exceptions import (
64
+ BrokenResourceError,
65
+ BusyResourceError,
66
+ ClosedResourceError,
67
+ EndOfStream,
68
+ )
69
+ from .._core._sockets import convert_ipv6_sockaddr
70
+ from .._core._streams import create_memory_object_stream
71
+ from .._core._synchronization import (
72
+ CapacityLimiter as BaseCapacityLimiter,
73
+ )
74
+ from .._core._synchronization import Event as BaseEvent
75
+ from .._core._synchronization import Lock as BaseLock
76
+ from .._core._synchronization import (
77
+ ResourceGuard,
78
+ SemaphoreStatistics,
79
+ )
80
+ from .._core._synchronization import Semaphore as BaseSemaphore
81
+ from .._core._tasks import CancelScope as BaseCancelScope
82
+ from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType
83
+ from ..abc._eventloop import AsyncBackend, StrOrBytesPath
84
+ from ..streams.memory import MemoryObjectSendStream
85
+
86
+ if TYPE_CHECKING:
87
+ from _typeshed import FileDescriptorLike
88
+
89
+ if sys.version_info >= (3, 10):
90
+ from typing import ParamSpec
91
+ else:
92
+ from typing_extensions import ParamSpec
93
+
94
+ if sys.version_info >= (3, 11):
95
+ from typing import TypeVarTuple, Unpack
96
+ else:
97
+ from exceptiongroup import BaseExceptionGroup
98
+ from typing_extensions import TypeVarTuple, Unpack
99
+
100
+ T = TypeVar("T")
101
+ T_Retval = TypeVar("T_Retval")
102
+ T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType)
103
+ PosArgsT = TypeVarTuple("PosArgsT")
104
+ P = ParamSpec("P")
105
+
106
+
107
+ #
108
+ # Event loop
109
+ #
110
+
111
+ RunVar = trio.lowlevel.RunVar
112
+
113
+
114
+ #
115
+ # Timeouts and cancellation
116
+ #
117
+
118
+
119
+ class CancelScope(BaseCancelScope):
120
+ def __new__(
121
+ cls, original: trio.CancelScope | None = None, **kwargs: object
122
+ ) -> CancelScope:
123
+ return object.__new__(cls)
124
+
125
+ def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None:
126
+ self.__original = original or trio.CancelScope(**kwargs)
127
+
128
+ def __enter__(self) -> CancelScope:
129
+ self.__original.__enter__()
130
+ return self
131
+
132
+ def __exit__(
133
+ self,
134
+ exc_type: type[BaseException] | None,
135
+ exc_val: BaseException | None,
136
+ exc_tb: TracebackType | None,
137
+ ) -> bool:
138
+ return self.__original.__exit__(exc_type, exc_val, exc_tb)
139
+
140
+ def cancel(self, reason: str | None = None) -> None:
141
+ self.__original.cancel(reason)
142
+
143
+ @property
144
+ def deadline(self) -> float:
145
+ return self.__original.deadline
146
+
147
+ @deadline.setter
148
+ def deadline(self, value: float) -> None:
149
+ self.__original.deadline = value
150
+
151
+ @property
152
+ def cancel_called(self) -> bool:
153
+ return self.__original.cancel_called
154
+
155
+ @property
156
+ def cancelled_caught(self) -> bool:
157
+ return self.__original.cancelled_caught
158
+
159
+ @property
160
+ def shield(self) -> bool:
161
+ return self.__original.shield
162
+
163
+ @shield.setter
164
+ def shield(self, value: bool) -> None:
165
+ self.__original.shield = value
166
+
167
+
168
+ #
169
+ # Task groups
170
+ #
171
+
172
+
173
+ class TaskGroup(abc.TaskGroup):
174
+ def __init__(self) -> None:
175
+ self._active = False
176
+ self._nursery_manager = trio.open_nursery(strict_exception_groups=True)
177
+ self.cancel_scope = None # type: ignore[assignment]
178
+
179
+ async def __aenter__(self) -> TaskGroup:
180
+ self._active = True
181
+ self._nursery = await self._nursery_manager.__aenter__()
182
+ self.cancel_scope = CancelScope(self._nursery.cancel_scope)
183
+ return self
184
+
185
+ async def __aexit__(
186
+ self,
187
+ exc_type: type[BaseException] | None,
188
+ exc_val: BaseException | None,
189
+ exc_tb: TracebackType | None,
190
+ ) -> bool:
191
+ try:
192
+ # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type
193
+ return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value]
194
+ except BaseExceptionGroup as exc:
195
+ if not exc.split(trio.Cancelled)[1]:
196
+ raise trio.Cancelled._create() from exc
197
+
198
+ raise
199
+ finally:
200
+ del exc_val, exc_tb
201
+ self._active = False
202
+
203
+ def start_soon(
204
+ self,
205
+ func: Callable[[Unpack[PosArgsT]], Awaitable[Any]],
206
+ *args: Unpack[PosArgsT],
207
+ name: object = None,
208
+ ) -> None:
209
+ if not self._active:
210
+ raise RuntimeError(
211
+ "This task group is not active; no new tasks can be started."
212
+ )
213
+
214
+ self._nursery.start_soon(func, *args, name=name)
215
+
216
+ async def start(
217
+ self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None
218
+ ) -> Any:
219
+ if not self._active:
220
+ raise RuntimeError(
221
+ "This task group is not active; no new tasks can be started."
222
+ )
223
+
224
+ return await self._nursery.start(func, *args, name=name)
225
+
226
+
227
+ #
228
+ # Threads
229
+ #
230
+
231
+
232
+ class BlockingPortal(abc.BlockingPortal):
233
+ def __new__(cls) -> BlockingPortal:
234
+ return object.__new__(cls)
235
+
236
+ def __init__(self) -> None:
237
+ super().__init__()
238
+ self._token = trio.lowlevel.current_trio_token()
239
+
240
+ def _spawn_task_from_thread(
241
+ self,
242
+ func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
243
+ args: tuple[Unpack[PosArgsT]],
244
+ kwargs: dict[str, Any],
245
+ name: object,
246
+ future: Future[T_Retval],
247
+ ) -> None:
248
+ trio.from_thread.run_sync(
249
+ partial(self._task_group.start_soon, name=name),
250
+ self._call_func,
251
+ func,
252
+ args,
253
+ kwargs,
254
+ future,
255
+ trio_token=self._token,
256
+ )
257
+
258
+
259
+ #
260
+ # Subprocesses
261
+ #
262
+
263
+
264
+ @dataclass(eq=False)
265
+ class ReceiveStreamWrapper(abc.ByteReceiveStream):
266
+ _stream: trio.abc.ReceiveStream
267
+
268
+ async def receive(self, max_bytes: int | None = None) -> bytes:
269
+ try:
270
+ data = await self._stream.receive_some(max_bytes)
271
+ except trio.ClosedResourceError as exc:
272
+ raise ClosedResourceError from exc.__cause__
273
+ except trio.BrokenResourceError as exc:
274
+ raise BrokenResourceError from exc.__cause__
275
+
276
+ if data:
277
+ return bytes(data)
278
+ else:
279
+ raise EndOfStream
280
+
281
+ async def aclose(self) -> None:
282
+ await self._stream.aclose()
283
+
284
+
285
+ @dataclass(eq=False)
286
+ class SendStreamWrapper(abc.ByteSendStream):
287
+ _stream: trio.abc.SendStream
288
+
289
+ async def send(self, item: bytes) -> None:
290
+ try:
291
+ await self._stream.send_all(item)
292
+ except trio.ClosedResourceError as exc:
293
+ raise ClosedResourceError from exc.__cause__
294
+ except trio.BrokenResourceError as exc:
295
+ raise BrokenResourceError from exc.__cause__
296
+
297
+ async def aclose(self) -> None:
298
+ await self._stream.aclose()
299
+
300
+
301
+ @dataclass(eq=False)
302
+ class Process(abc.Process):
303
+ _process: trio.Process
304
+ _stdin: abc.ByteSendStream | None
305
+ _stdout: abc.ByteReceiveStream | None
306
+ _stderr: abc.ByteReceiveStream | None
307
+
308
+ async def aclose(self) -> None:
309
+ with CancelScope(shield=True):
310
+ if self._stdin:
311
+ await self._stdin.aclose()
312
+ if self._stdout:
313
+ await self._stdout.aclose()
314
+ if self._stderr:
315
+ await self._stderr.aclose()
316
+
317
+ try:
318
+ await self.wait()
319
+ except BaseException:
320
+ self.kill()
321
+ with CancelScope(shield=True):
322
+ await self.wait()
323
+ raise
324
+
325
+ async def wait(self) -> int:
326
+ return await self._process.wait()
327
+
328
+ def terminate(self) -> None:
329
+ self._process.terminate()
330
+
331
+ def kill(self) -> None:
332
+ self._process.kill()
333
+
334
+ def send_signal(self, signal: Signals) -> None:
335
+ self._process.send_signal(signal)
336
+
337
+ @property
338
+ def pid(self) -> int:
339
+ return self._process.pid
340
+
341
+ @property
342
+ def returncode(self) -> int | None:
343
+ return self._process.returncode
344
+
345
+ @property
346
+ def stdin(self) -> abc.ByteSendStream | None:
347
+ return self._stdin
348
+
349
+ @property
350
+ def stdout(self) -> abc.ByteReceiveStream | None:
351
+ return self._stdout
352
+
353
+ @property
354
+ def stderr(self) -> abc.ByteReceiveStream | None:
355
+ return self._stderr
356
+
357
+
358
+ class _ProcessPoolShutdownInstrument(trio.abc.Instrument):
359
+ def after_run(self) -> None:
360
+ super().after_run()
361
+
362
+
363
+ current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar(
364
+ "current_default_worker_process_limiter"
365
+ )
366
+
367
+
368
+ async def _shutdown_process_pool(workers: set[abc.Process]) -> None:
369
+ try:
370
+ await trio.sleep(math.inf)
371
+ except trio.Cancelled:
372
+ for process in workers:
373
+ if process.returncode is None:
374
+ process.kill()
375
+
376
+ with CancelScope(shield=True):
377
+ for process in workers:
378
+ await process.aclose()
379
+
380
+
381
+ #
382
+ # Sockets and networking
383
+ #
384
+
385
+
386
+ class _TrioSocketMixin(Generic[T_SockAddr]):
387
+ def __init__(self, trio_socket: TrioSocketType) -> None:
388
+ self._trio_socket = trio_socket
389
+ self._closed = False
390
+
391
+ def _check_closed(self) -> None:
392
+ if self._closed:
393
+ raise ClosedResourceError
394
+ if self._trio_socket.fileno() < 0:
395
+ raise BrokenResourceError
396
+
397
+ @property
398
+ def _raw_socket(self) -> socket.socket:
399
+ return self._trio_socket._sock # type: ignore[attr-defined]
400
+
401
+ async def aclose(self) -> None:
402
+ if self._trio_socket.fileno() >= 0:
403
+ self._closed = True
404
+ self._trio_socket.close()
405
+
406
+ def _convert_socket_error(self, exc: BaseException) -> NoReturn:
407
+ if isinstance(exc, trio.ClosedResourceError):
408
+ raise ClosedResourceError from exc
409
+ elif self._trio_socket.fileno() < 0 and self._closed:
410
+ raise ClosedResourceError from None
411
+ elif isinstance(exc, OSError):
412
+ raise BrokenResourceError from exc
413
+ else:
414
+ raise exc
415
+
416
+
417
+ class SocketStream(_TrioSocketMixin, abc.SocketStream):
418
+ def __init__(self, trio_socket: TrioSocketType) -> None:
419
+ super().__init__(trio_socket)
420
+ self._receive_guard = ResourceGuard("reading from")
421
+ self._send_guard = ResourceGuard("writing to")
422
+
423
+ async def receive(self, max_bytes: int = 65536) -> bytes:
424
+ with self._receive_guard:
425
+ try:
426
+ data = await self._trio_socket.recv(max_bytes)
427
+ except BaseException as exc:
428
+ self._convert_socket_error(exc)
429
+
430
+ if data:
431
+ return data
432
+ else:
433
+ raise EndOfStream
434
+
435
+ async def send(self, item: bytes) -> None:
436
+ with self._send_guard:
437
+ view = memoryview(item)
438
+ while view:
439
+ try:
440
+ bytes_sent = await self._trio_socket.send(view)
441
+ except BaseException as exc:
442
+ self._convert_socket_error(exc)
443
+
444
+ view = view[bytes_sent:]
445
+
446
+ async def send_eof(self) -> None:
447
+ self._trio_socket.shutdown(socket.SHUT_WR)
448
+
449
+
450
+ class UNIXSocketStream(SocketStream, abc.UNIXSocketStream):
451
+ async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
452
+ if not isinstance(msglen, int) or msglen < 0:
453
+ raise ValueError("msglen must be a non-negative integer")
454
+ if not isinstance(maxfds, int) or maxfds < 1:
455
+ raise ValueError("maxfds must be a positive integer")
456
+
457
+ fds = array.array("i")
458
+ await trio.lowlevel.checkpoint()
459
+ with self._receive_guard:
460
+ while True:
461
+ try:
462
+ message, ancdata, flags, addr = await self._trio_socket.recvmsg(
463
+ msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
464
+ )
465
+ except BaseException as exc:
466
+ self._convert_socket_error(exc)
467
+ else:
468
+ if not message and not ancdata:
469
+ raise EndOfStream
470
+
471
+ break
472
+
473
+ for cmsg_level, cmsg_type, cmsg_data in ancdata:
474
+ if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
475
+ raise RuntimeError(
476
+ f"Received unexpected ancillary data; message = {message!r}, "
477
+ f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
478
+ )
479
+
480
+ fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
481
+
482
+ return message, list(fds)
483
+
484
+ async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
485
+ if not message:
486
+ raise ValueError("message must not be empty")
487
+ if not fds:
488
+ raise ValueError("fds must not be empty")
489
+
490
+ filenos: list[int] = []
491
+ for fd in fds:
492
+ if isinstance(fd, int):
493
+ filenos.append(fd)
494
+ elif isinstance(fd, IOBase):
495
+ filenos.append(fd.fileno())
496
+
497
+ fdarray = array.array("i", filenos)
498
+ await trio.lowlevel.checkpoint()
499
+ with self._send_guard:
500
+ while True:
501
+ try:
502
+ await self._trio_socket.sendmsg(
503
+ [message],
504
+ [
505
+ (
506
+ socket.SOL_SOCKET,
507
+ socket.SCM_RIGHTS,
508
+ fdarray,
509
+ )
510
+ ],
511
+ )
512
+ break
513
+ except BaseException as exc:
514
+ self._convert_socket_error(exc)
515
+
516
+
517
+ class TCPSocketListener(_TrioSocketMixin, abc.SocketListener):
518
+ def __init__(self, raw_socket: socket.socket):
519
+ super().__init__(trio.socket.from_stdlib_socket(raw_socket))
520
+ self._accept_guard = ResourceGuard("accepting connections from")
521
+
522
+ async def accept(self) -> SocketStream:
523
+ with self._accept_guard:
524
+ try:
525
+ trio_socket, _addr = await self._trio_socket.accept()
526
+ except BaseException as exc:
527
+ self._convert_socket_error(exc)
528
+
529
+ trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
530
+ return SocketStream(trio_socket)
531
+
532
+
533
+ class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener):
534
+ def __init__(self, raw_socket: socket.socket):
535
+ super().__init__(trio.socket.from_stdlib_socket(raw_socket))
536
+ self._accept_guard = ResourceGuard("accepting connections from")
537
+
538
+ async def accept(self) -> UNIXSocketStream:
539
+ with self._accept_guard:
540
+ try:
541
+ trio_socket, _addr = await self._trio_socket.accept()
542
+ except BaseException as exc:
543
+ self._convert_socket_error(exc)
544
+
545
+ return UNIXSocketStream(trio_socket)
546
+
547
+
548
+ class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket):
549
+ def __init__(self, trio_socket: TrioSocketType) -> None:
550
+ super().__init__(trio_socket)
551
+ self._receive_guard = ResourceGuard("reading from")
552
+ self._send_guard = ResourceGuard("writing to")
553
+
554
+ async def receive(self) -> tuple[bytes, IPSockAddrType]:
555
+ with self._receive_guard:
556
+ try:
557
+ data, addr = await self._trio_socket.recvfrom(65536)
558
+ return data, convert_ipv6_sockaddr(addr)
559
+ except BaseException as exc:
560
+ self._convert_socket_error(exc)
561
+
562
+ async def send(self, item: UDPPacketType) -> None:
563
+ with self._send_guard:
564
+ try:
565
+ await self._trio_socket.sendto(*item)
566
+ except BaseException as exc:
567
+ self._convert_socket_error(exc)
568
+
569
+
570
+ class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket):
571
+ def __init__(self, trio_socket: TrioSocketType) -> None:
572
+ super().__init__(trio_socket)
573
+ self._receive_guard = ResourceGuard("reading from")
574
+ self._send_guard = ResourceGuard("writing to")
575
+
576
+ async def receive(self) -> bytes:
577
+ with self._receive_guard:
578
+ try:
579
+ return await self._trio_socket.recv(65536)
580
+ except BaseException as exc:
581
+ self._convert_socket_error(exc)
582
+
583
+ async def send(self, item: bytes) -> None:
584
+ with self._send_guard:
585
+ try:
586
+ await self._trio_socket.send(item)
587
+ except BaseException as exc:
588
+ self._convert_socket_error(exc)
589
+
590
+
591
+ class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket):
592
+ def __init__(self, trio_socket: TrioSocketType) -> None:
593
+ super().__init__(trio_socket)
594
+ self._receive_guard = ResourceGuard("reading from")
595
+ self._send_guard = ResourceGuard("writing to")
596
+
597
+ async def receive(self) -> UNIXDatagramPacketType:
598
+ with self._receive_guard:
599
+ try:
600
+ data, addr = await self._trio_socket.recvfrom(65536)
601
+ return data, addr
602
+ except BaseException as exc:
603
+ self._convert_socket_error(exc)
604
+
605
+ async def send(self, item: UNIXDatagramPacketType) -> None:
606
+ with self._send_guard:
607
+ try:
608
+ await self._trio_socket.sendto(*item)
609
+ except BaseException as exc:
610
+ self._convert_socket_error(exc)
611
+
612
+
613
+ class ConnectedUNIXDatagramSocket(
614
+ _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket
615
+ ):
616
+ def __init__(self, trio_socket: TrioSocketType) -> None:
617
+ super().__init__(trio_socket)
618
+ self._receive_guard = ResourceGuard("reading from")
619
+ self._send_guard = ResourceGuard("writing to")
620
+
621
+ async def receive(self) -> bytes:
622
+ with self._receive_guard:
623
+ try:
624
+ return await self._trio_socket.recv(65536)
625
+ except BaseException as exc:
626
+ self._convert_socket_error(exc)
627
+
628
+ async def send(self, item: bytes) -> None:
629
+ with self._send_guard:
630
+ try:
631
+ await self._trio_socket.send(item)
632
+ except BaseException as exc:
633
+ self._convert_socket_error(exc)
634
+
635
+
636
+ #
637
+ # Synchronization
638
+ #
639
+
640
+
641
+ class Event(BaseEvent):
642
+ def __new__(cls) -> Event:
643
+ return object.__new__(cls)
644
+
645
+ def __init__(self) -> None:
646
+ self.__original = trio.Event()
647
+
648
+ def is_set(self) -> bool:
649
+ return self.__original.is_set()
650
+
651
+ async def wait(self) -> None:
652
+ return await self.__original.wait()
653
+
654
+ def statistics(self) -> EventStatistics:
655
+ orig_statistics = self.__original.statistics()
656
+ return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting)
657
+
658
+ def set(self) -> None:
659
+ self.__original.set()
660
+
661
+
662
+ class Lock(BaseLock):
663
+ def __new__(cls, *, fast_acquire: bool = False) -> Lock:
664
+ return object.__new__(cls)
665
+
666
+ def __init__(self, *, fast_acquire: bool = False) -> None:
667
+ self._fast_acquire = fast_acquire
668
+ self.__original = trio.Lock()
669
+
670
+ @staticmethod
671
+ def _convert_runtime_error_msg(exc: RuntimeError) -> None:
672
+ if exc.args == ("attempt to re-acquire an already held Lock",):
673
+ exc.args = ("Attempted to acquire an already held Lock",)
674
+
675
+ async def acquire(self) -> None:
676
+ if not self._fast_acquire:
677
+ try:
678
+ await self.__original.acquire()
679
+ except RuntimeError as exc:
680
+ self._convert_runtime_error_msg(exc)
681
+ raise
682
+
683
+ return
684
+
685
+ # This is the "fast path" where we don't let other tasks run
686
+ await trio.lowlevel.checkpoint_if_cancelled()
687
+ try:
688
+ self.__original.acquire_nowait()
689
+ except trio.WouldBlock:
690
+ await self.__original._lot.park()
691
+ except RuntimeError as exc:
692
+ self._convert_runtime_error_msg(exc)
693
+ raise
694
+
695
+ def acquire_nowait(self) -> None:
696
+ try:
697
+ self.__original.acquire_nowait()
698
+ except trio.WouldBlock:
699
+ raise WouldBlock from None
700
+ except RuntimeError as exc:
701
+ self._convert_runtime_error_msg(exc)
702
+ raise
703
+
704
+ def locked(self) -> bool:
705
+ return self.__original.locked()
706
+
707
+ def release(self) -> None:
708
+ self.__original.release()
709
+
710
+ def statistics(self) -> LockStatistics:
711
+ orig_statistics = self.__original.statistics()
712
+ owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None
713
+ return LockStatistics(
714
+ orig_statistics.locked, owner, orig_statistics.tasks_waiting
715
+ )
716
+
717
+
718
+ class Semaphore(BaseSemaphore):
719
+ def __new__(
720
+ cls,
721
+ initial_value: int,
722
+ *,
723
+ max_value: int | None = None,
724
+ fast_acquire: bool = False,
725
+ ) -> Semaphore:
726
+ return object.__new__(cls)
727
+
728
+ def __init__(
729
+ self,
730
+ initial_value: int,
731
+ *,
732
+ max_value: int | None = None,
733
+ fast_acquire: bool = False,
734
+ ) -> None:
735
+ super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
736
+ self.__original = trio.Semaphore(initial_value, max_value=max_value)
737
+
738
+ async def acquire(self) -> None:
739
+ if not self._fast_acquire:
740
+ await self.__original.acquire()
741
+ return
742
+
743
+ # This is the "fast path" where we don't let other tasks run
744
+ await trio.lowlevel.checkpoint_if_cancelled()
745
+ try:
746
+ self.__original.acquire_nowait()
747
+ except trio.WouldBlock:
748
+ await self.__original._lot.park()
749
+
750
+ def acquire_nowait(self) -> None:
751
+ try:
752
+ self.__original.acquire_nowait()
753
+ except trio.WouldBlock:
754
+ raise WouldBlock from None
755
+
756
+ @property
757
+ def max_value(self) -> int | None:
758
+ return self.__original.max_value
759
+
760
+ @property
761
+ def value(self) -> int:
762
+ return self.__original.value
763
+
764
+ def release(self) -> None:
765
+ self.__original.release()
766
+
767
+ def statistics(self) -> SemaphoreStatistics:
768
+ orig_statistics = self.__original.statistics()
769
+ return SemaphoreStatistics(orig_statistics.tasks_waiting)
770
+
771
+
772
+ class CapacityLimiter(BaseCapacityLimiter):
773
+ def __new__(
774
+ cls,
775
+ total_tokens: float | None = None,
776
+ *,
777
+ original: trio.CapacityLimiter | None = None,
778
+ ) -> CapacityLimiter:
779
+ return object.__new__(cls)
780
+
781
+ def __init__(
782
+ self,
783
+ total_tokens: float | None = None,
784
+ *,
785
+ original: trio.CapacityLimiter | None = None,
786
+ ) -> None:
787
+ if original is not None:
788
+ self.__original = original
789
+ else:
790
+ assert total_tokens is not None
791
+ self.__original = trio.CapacityLimiter(total_tokens)
792
+
793
+ async def __aenter__(self) -> None:
794
+ return await self.__original.__aenter__()
795
+
796
+ async def __aexit__(
797
+ self,
798
+ exc_type: type[BaseException] | None,
799
+ exc_val: BaseException | None,
800
+ exc_tb: TracebackType | None,
801
+ ) -> None:
802
+ await self.__original.__aexit__(exc_type, exc_val, exc_tb)
803
+
804
+ @property
805
+ def total_tokens(self) -> float:
806
+ return self.__original.total_tokens
807
+
808
+ @total_tokens.setter
809
+ def total_tokens(self, value: float) -> None:
810
+ self.__original.total_tokens = value
811
+
812
+ @property
813
+ def borrowed_tokens(self) -> int:
814
+ return self.__original.borrowed_tokens
815
+
816
+ @property
817
+ def available_tokens(self) -> float:
818
+ return self.__original.available_tokens
819
+
820
+ def acquire_nowait(self) -> None:
821
+ self.__original.acquire_nowait()
822
+
823
+ def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
824
+ self.__original.acquire_on_behalf_of_nowait(borrower)
825
+
826
+ async def acquire(self) -> None:
827
+ await self.__original.acquire()
828
+
829
+ async def acquire_on_behalf_of(self, borrower: object) -> None:
830
+ await self.__original.acquire_on_behalf_of(borrower)
831
+
832
+ def release(self) -> None:
833
+ return self.__original.release()
834
+
835
+ def release_on_behalf_of(self, borrower: object) -> None:
836
+ return self.__original.release_on_behalf_of(borrower)
837
+
838
+ def statistics(self) -> CapacityLimiterStatistics:
839
+ orig = self.__original.statistics()
840
+ return CapacityLimiterStatistics(
841
+ borrowed_tokens=orig.borrowed_tokens,
842
+ total_tokens=orig.total_tokens,
843
+ borrowers=tuple(orig.borrowers),
844
+ tasks_waiting=orig.tasks_waiting,
845
+ )
846
+
847
+
848
+ _capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper")
849
+
850
+
851
+ #
852
+ # Signal handling
853
+ #
854
+
855
+
856
+ class _SignalReceiver:
857
+ _iterator: AsyncIterator[int]
858
+
859
+ def __init__(self, signals: tuple[Signals, ...]):
860
+ self._signals = signals
861
+
862
+ def __enter__(self) -> _SignalReceiver:
863
+ self._cm = trio.open_signal_receiver(*self._signals)
864
+ self._iterator = self._cm.__enter__()
865
+ return self
866
+
867
+ def __exit__(
868
+ self,
869
+ exc_type: type[BaseException] | None,
870
+ exc_val: BaseException | None,
871
+ exc_tb: TracebackType | None,
872
+ ) -> bool | None:
873
+ return self._cm.__exit__(exc_type, exc_val, exc_tb)
874
+
875
+ def __aiter__(self) -> _SignalReceiver:
876
+ return self
877
+
878
+ async def __anext__(self) -> Signals:
879
+ signum = await self._iterator.__anext__()
880
+ return Signals(signum)
881
+
882
+
883
+ #
884
+ # Testing and debugging
885
+ #
886
+
887
+
888
+ class TestRunner(abc.TestRunner):
889
+ def __init__(self, **options: Any) -> None:
890
+ from queue import Queue
891
+
892
+ self._call_queue: Queue[Callable[[], object]] = Queue()
893
+ self._send_stream: MemoryObjectSendStream | None = None
894
+ self._options = options
895
+
896
+ def __exit__(
897
+ self,
898
+ exc_type: type[BaseException] | None,
899
+ exc_val: BaseException | None,
900
+ exc_tb: types.TracebackType | None,
901
+ ) -> None:
902
+ if self._send_stream:
903
+ self._send_stream.close()
904
+ while self._send_stream is not None:
905
+ self._call_queue.get()()
906
+
907
+ async def _run_tests_and_fixtures(self) -> None:
908
+ self._send_stream, receive_stream = create_memory_object_stream(1)
909
+ with receive_stream:
910
+ async for coro, outcome_holder in receive_stream:
911
+ try:
912
+ retval = await coro
913
+ except BaseException as exc:
914
+ outcome_holder.append(Error(exc))
915
+ else:
916
+ outcome_holder.append(Value(retval))
917
+
918
+ def _main_task_finished(self, outcome: object) -> None:
919
+ self._send_stream = None
920
+
921
+ def _call_in_runner_task(
922
+ self,
923
+ func: Callable[P, Awaitable[T_Retval]],
924
+ *args: P.args,
925
+ **kwargs: P.kwargs,
926
+ ) -> T_Retval:
927
+ if self._send_stream is None:
928
+ trio.lowlevel.start_guest_run(
929
+ self._run_tests_and_fixtures,
930
+ run_sync_soon_threadsafe=self._call_queue.put,
931
+ done_callback=self._main_task_finished,
932
+ **self._options,
933
+ )
934
+ while self._send_stream is None:
935
+ self._call_queue.get()()
936
+
937
+ outcome_holder: list[Outcome] = []
938
+ self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder))
939
+ while not outcome_holder:
940
+ self._call_queue.get()()
941
+
942
+ return outcome_holder[0].unwrap()
943
+
944
+ def run_asyncgen_fixture(
945
+ self,
946
+ fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
947
+ kwargs: dict[str, Any],
948
+ ) -> Iterable[T_Retval]:
949
+ asyncgen = fixture_func(**kwargs)
950
+ fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None)
951
+
952
+ yield fixturevalue
953
+
954
+ try:
955
+ self._call_in_runner_task(asyncgen.asend, None)
956
+ except StopAsyncIteration:
957
+ pass
958
+ else:
959
+ self._call_in_runner_task(asyncgen.aclose)
960
+ raise RuntimeError("Async generator fixture did not stop")
961
+
962
+ def run_fixture(
963
+ self,
964
+ fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
965
+ kwargs: dict[str, Any],
966
+ ) -> T_Retval:
967
+ return self._call_in_runner_task(fixture_func, **kwargs)
968
+
969
+ def run_test(
970
+ self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
971
+ ) -> None:
972
+ self._call_in_runner_task(test_func, **kwargs)
973
+
974
+
975
+ class TrioTaskInfo(TaskInfo):
976
+ def __init__(self, task: trio.lowlevel.Task):
977
+ parent_id = None
978
+ if task.parent_nursery and task.parent_nursery.parent_task:
979
+ parent_id = id(task.parent_nursery.parent_task)
980
+
981
+ super().__init__(id(task), parent_id, task.name, task.coro)
982
+ self._task = weakref.proxy(task)
983
+
984
+ def has_pending_cancellation(self) -> bool:
985
+ try:
986
+ return self._task._cancel_status.effectively_cancelled
987
+ except ReferenceError:
988
+ # If the task is no longer around, it surely doesn't have a cancellation
989
+ # pending
990
+ return False
991
+
992
+
993
+ class TrioBackend(AsyncBackend):
994
+ @classmethod
995
+ def run(
996
+ cls,
997
+ func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
998
+ args: tuple[Unpack[PosArgsT]],
999
+ kwargs: dict[str, Any],
1000
+ options: dict[str, Any],
1001
+ ) -> T_Retval:
1002
+ return trio.run(func, *args)
1003
+
1004
+ @classmethod
1005
+ def current_token(cls) -> object:
1006
+ return trio.lowlevel.current_trio_token()
1007
+
1008
+ @classmethod
1009
+ def current_time(cls) -> float:
1010
+ return trio.current_time()
1011
+
1012
+ @classmethod
1013
+ def cancelled_exception_class(cls) -> type[BaseException]:
1014
+ return trio.Cancelled
1015
+
1016
+ @classmethod
1017
+ async def checkpoint(cls) -> None:
1018
+ await trio.lowlevel.checkpoint()
1019
+
1020
+ @classmethod
1021
+ async def checkpoint_if_cancelled(cls) -> None:
1022
+ await trio.lowlevel.checkpoint_if_cancelled()
1023
+
1024
+ @classmethod
1025
+ async def cancel_shielded_checkpoint(cls) -> None:
1026
+ await trio.lowlevel.cancel_shielded_checkpoint()
1027
+
1028
+ @classmethod
1029
+ async def sleep(cls, delay: float) -> None:
1030
+ await trio.sleep(delay)
1031
+
1032
+ @classmethod
1033
+ def create_cancel_scope(
1034
+ cls, *, deadline: float = math.inf, shield: bool = False
1035
+ ) -> abc.CancelScope:
1036
+ return CancelScope(deadline=deadline, shield=shield)
1037
+
1038
+ @classmethod
1039
+ def current_effective_deadline(cls) -> float:
1040
+ return trio.current_effective_deadline()
1041
+
1042
+ @classmethod
1043
+ def create_task_group(cls) -> abc.TaskGroup:
1044
+ return TaskGroup()
1045
+
1046
+ @classmethod
1047
+ def create_event(cls) -> abc.Event:
1048
+ return Event()
1049
+
1050
+ @classmethod
1051
+ def create_lock(cls, *, fast_acquire: bool) -> Lock:
1052
+ return Lock(fast_acquire=fast_acquire)
1053
+
1054
+ @classmethod
1055
+ def create_semaphore(
1056
+ cls,
1057
+ initial_value: int,
1058
+ *,
1059
+ max_value: int | None = None,
1060
+ fast_acquire: bool = False,
1061
+ ) -> abc.Semaphore:
1062
+ return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
1063
+
1064
+ @classmethod
1065
+ def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter:
1066
+ return CapacityLimiter(total_tokens)
1067
+
1068
+ @classmethod
1069
+ async def run_sync_in_worker_thread(
1070
+ cls,
1071
+ func: Callable[[Unpack[PosArgsT]], T_Retval],
1072
+ args: tuple[Unpack[PosArgsT]],
1073
+ abandon_on_cancel: bool = False,
1074
+ limiter: abc.CapacityLimiter | None = None,
1075
+ ) -> T_Retval:
1076
+ def wrapper() -> T_Retval:
1077
+ with claim_worker_thread(TrioBackend, token):
1078
+ return func(*args)
1079
+
1080
+ token = TrioBackend.current_token()
1081
+ return await run_sync(
1082
+ wrapper,
1083
+ abandon_on_cancel=abandon_on_cancel,
1084
+ limiter=cast(trio.CapacityLimiter, limiter),
1085
+ )
1086
+
1087
+ @classmethod
1088
+ def check_cancelled(cls) -> None:
1089
+ trio.from_thread.check_cancelled()
1090
+
1091
+ @classmethod
1092
+ def run_async_from_thread(
1093
+ cls,
1094
+ func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
1095
+ args: tuple[Unpack[PosArgsT]],
1096
+ token: object,
1097
+ ) -> T_Retval:
1098
+ trio_token = cast("trio.lowlevel.TrioToken | None", token)
1099
+ try:
1100
+ return trio.from_thread.run(func, *args, trio_token=trio_token)
1101
+ except trio.RunFinishedError:
1102
+ raise RunFinishedError from None
1103
+
1104
+ @classmethod
1105
+ def run_sync_from_thread(
1106
+ cls,
1107
+ func: Callable[[Unpack[PosArgsT]], T_Retval],
1108
+ args: tuple[Unpack[PosArgsT]],
1109
+ token: object,
1110
+ ) -> T_Retval:
1111
+ trio_token = cast("trio.lowlevel.TrioToken | None", token)
1112
+ try:
1113
+ return trio.from_thread.run_sync(func, *args, trio_token=trio_token)
1114
+ except trio.RunFinishedError:
1115
+ raise RunFinishedError from None
1116
+
1117
+ @classmethod
1118
+ def create_blocking_portal(cls) -> abc.BlockingPortal:
1119
+ return BlockingPortal()
1120
+
1121
+ @classmethod
1122
+ async def open_process(
1123
+ cls,
1124
+ command: StrOrBytesPath | Sequence[StrOrBytesPath],
1125
+ *,
1126
+ stdin: int | IO[Any] | None,
1127
+ stdout: int | IO[Any] | None,
1128
+ stderr: int | IO[Any] | None,
1129
+ **kwargs: Any,
1130
+ ) -> Process:
1131
+ def convert_item(item: StrOrBytesPath) -> str:
1132
+ str_or_bytes = os.fspath(item)
1133
+ if isinstance(str_or_bytes, str):
1134
+ return str_or_bytes
1135
+ else:
1136
+ return os.fsdecode(str_or_bytes)
1137
+
1138
+ if isinstance(command, (str, bytes, PathLike)):
1139
+ process = await trio.lowlevel.open_process(
1140
+ convert_item(command),
1141
+ stdin=stdin,
1142
+ stdout=stdout,
1143
+ stderr=stderr,
1144
+ shell=True,
1145
+ **kwargs,
1146
+ )
1147
+ else:
1148
+ process = await trio.lowlevel.open_process(
1149
+ [convert_item(item) for item in command],
1150
+ stdin=stdin,
1151
+ stdout=stdout,
1152
+ stderr=stderr,
1153
+ shell=False,
1154
+ **kwargs,
1155
+ )
1156
+
1157
+ stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None
1158
+ stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None
1159
+ stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None
1160
+ return Process(process, stdin_stream, stdout_stream, stderr_stream)
1161
+
1162
+ @classmethod
1163
+ def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
1164
+ trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers)
1165
+
1166
+ @classmethod
1167
+ async def connect_tcp(
1168
+ cls, host: str, port: int, local_address: IPSockAddrType | None = None
1169
+ ) -> SocketStream:
1170
+ family = socket.AF_INET6 if ":" in host else socket.AF_INET
1171
+ trio_socket = trio.socket.socket(family)
1172
+ trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1173
+ if local_address:
1174
+ await trio_socket.bind(local_address)
1175
+
1176
+ try:
1177
+ await trio_socket.connect((host, port))
1178
+ except BaseException:
1179
+ trio_socket.close()
1180
+ raise
1181
+
1182
+ return SocketStream(trio_socket)
1183
+
1184
+ @classmethod
1185
+ async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
1186
+ trio_socket = trio.socket.socket(socket.AF_UNIX)
1187
+ try:
1188
+ await trio_socket.connect(path)
1189
+ except BaseException:
1190
+ trio_socket.close()
1191
+ raise
1192
+
1193
+ return UNIXSocketStream(trio_socket)
1194
+
1195
+ @classmethod
1196
+ def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener:
1197
+ return TCPSocketListener(sock)
1198
+
1199
+ @classmethod
1200
+ def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener:
1201
+ return UNIXSocketListener(sock)
1202
+
1203
+ @classmethod
1204
+ async def create_udp_socket(
1205
+ cls,
1206
+ family: socket.AddressFamily,
1207
+ local_address: IPSockAddrType | None,
1208
+ remote_address: IPSockAddrType | None,
1209
+ reuse_port: bool,
1210
+ ) -> UDPSocket | ConnectedUDPSocket:
1211
+ trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM)
1212
+
1213
+ if reuse_port:
1214
+ trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1215
+
1216
+ if local_address:
1217
+ await trio_socket.bind(local_address)
1218
+
1219
+ if remote_address:
1220
+ await trio_socket.connect(remote_address)
1221
+ return ConnectedUDPSocket(trio_socket)
1222
+ else:
1223
+ return UDPSocket(trio_socket)
1224
+
1225
+ @classmethod
1226
+ @overload
1227
+ async def create_unix_datagram_socket(
1228
+ cls, raw_socket: socket.socket, remote_path: None
1229
+ ) -> abc.UNIXDatagramSocket: ...
1230
+
1231
+ @classmethod
1232
+ @overload
1233
+ async def create_unix_datagram_socket(
1234
+ cls, raw_socket: socket.socket, remote_path: str | bytes
1235
+ ) -> abc.ConnectedUNIXDatagramSocket: ...
1236
+
1237
+ @classmethod
1238
+ async def create_unix_datagram_socket(
1239
+ cls, raw_socket: socket.socket, remote_path: str | bytes | None
1240
+ ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
1241
+ trio_socket = trio.socket.from_stdlib_socket(raw_socket)
1242
+
1243
+ if remote_path:
1244
+ await trio_socket.connect(remote_path)
1245
+ return ConnectedUNIXDatagramSocket(trio_socket)
1246
+ else:
1247
+ return UNIXDatagramSocket(trio_socket)
1248
+
1249
+ @classmethod
1250
+ async def getaddrinfo(
1251
+ cls,
1252
+ host: bytes | str | None,
1253
+ port: str | int | None,
1254
+ *,
1255
+ family: int | AddressFamily = 0,
1256
+ type: int | SocketKind = 0,
1257
+ proto: int = 0,
1258
+ flags: int = 0,
1259
+ ) -> Sequence[
1260
+ tuple[
1261
+ AddressFamily,
1262
+ SocketKind,
1263
+ int,
1264
+ str,
1265
+ tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
1266
+ ]
1267
+ ]:
1268
+ return await trio.socket.getaddrinfo(host, port, family, type, proto, flags)
1269
+
1270
+ @classmethod
1271
+ async def getnameinfo(
1272
+ cls, sockaddr: IPSockAddrType, flags: int = 0
1273
+ ) -> tuple[str, str]:
1274
+ return await trio.socket.getnameinfo(sockaddr, flags)
1275
+
1276
+ @classmethod
1277
+ async def wait_readable(cls, obj: FileDescriptorLike) -> None:
1278
+ try:
1279
+ await wait_readable(obj)
1280
+ except trio.ClosedResourceError as exc:
1281
+ raise ClosedResourceError().with_traceback(exc.__traceback__) from None
1282
+ except trio.BusyResourceError:
1283
+ raise BusyResourceError("reading from") from None
1284
+
1285
+ @classmethod
1286
+ async def wait_writable(cls, obj: FileDescriptorLike) -> None:
1287
+ try:
1288
+ await wait_writable(obj)
1289
+ except trio.ClosedResourceError as exc:
1290
+ raise ClosedResourceError().with_traceback(exc.__traceback__) from None
1291
+ except trio.BusyResourceError:
1292
+ raise BusyResourceError("writing to") from None
1293
+
1294
+ @classmethod
1295
+ def notify_closing(cls, obj: FileDescriptorLike) -> None:
1296
+ notify_closing(obj)
1297
+
1298
+ @classmethod
1299
+ async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener:
1300
+ return TCPSocketListener(sock)
1301
+
1302
+ @classmethod
1303
+ async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
1304
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1305
+ return SocketStream(trio_sock)
1306
+
1307
+ @classmethod
1308
+ async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
1309
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1310
+ return UNIXSocketStream(trio_sock)
1311
+
1312
+ @classmethod
1313
+ async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
1314
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1315
+ return UDPSocket(trio_sock)
1316
+
1317
+ @classmethod
1318
+ async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
1319
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1320
+ return ConnectedUDPSocket(trio_sock)
1321
+
1322
+ @classmethod
1323
+ async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
1324
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1325
+ return UNIXDatagramSocket(trio_sock)
1326
+
1327
+ @classmethod
1328
+ async def wrap_connected_unix_datagram_socket(
1329
+ cls, sock: socket.socket
1330
+ ) -> ConnectedUNIXDatagramSocket:
1331
+ trio_sock = trio.socket.from_stdlib_socket(sock)
1332
+ return ConnectedUNIXDatagramSocket(trio_sock)
1333
+
1334
+ @classmethod
1335
+ def current_default_thread_limiter(cls) -> CapacityLimiter:
1336
+ try:
1337
+ return _capacity_limiter_wrapper.get()
1338
+ except LookupError:
1339
+ limiter = CapacityLimiter(
1340
+ original=trio.to_thread.current_default_thread_limiter()
1341
+ )
1342
+ _capacity_limiter_wrapper.set(limiter)
1343
+ return limiter
1344
+
1345
+ @classmethod
1346
+ def open_signal_receiver(
1347
+ cls, *signals: Signals
1348
+ ) -> AbstractContextManager[AsyncIterator[Signals]]:
1349
+ return _SignalReceiver(signals)
1350
+
1351
+ @classmethod
1352
+ def get_current_task(cls) -> TaskInfo:
1353
+ task = current_task()
1354
+ return TrioTaskInfo(task)
1355
+
1356
+ @classmethod
1357
+ def get_running_tasks(cls) -> Sequence[TaskInfo]:
1358
+ root_task = current_root_task()
1359
+ assert root_task
1360
+ task_infos = [TrioTaskInfo(root_task)]
1361
+ nurseries = root_task.child_nurseries
1362
+ while nurseries:
1363
+ new_nurseries: list[trio.Nursery] = []
1364
+ for nursery in nurseries:
1365
+ for task in nursery.child_tasks:
1366
+ task_infos.append(TrioTaskInfo(task))
1367
+ new_nurseries.extend(task.child_nurseries)
1368
+
1369
+ nurseries = new_nurseries
1370
+
1371
+ return task_infos
1372
+
1373
+ @classmethod
1374
+ async def wait_all_tasks_blocked(cls) -> None:
1375
+ from trio.testing import wait_all_tasks_blocked
1376
+
1377
+ await wait_all_tasks_blocked()
1378
+
1379
+ @classmethod
1380
+ def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
1381
+ return TestRunner(**options)
1382
+
1383
+
1384
+ backend_class = TrioBackend
backend/.venv/Lib/site-packages/anyio/_core/__init__.py ADDED
File without changes
backend/.venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import socket
5
+ import threading
6
+ from collections.abc import Callable
7
+ from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ if TYPE_CHECKING:
11
+ from _typeshed import FileDescriptorLike
12
+
13
+ _selector_lock = threading.Lock()
14
+ _selector: Selector | None = None
15
+
16
+
17
+ class Selector:
18
+ def __init__(self) -> None:
19
+ self._thread = threading.Thread(target=self.run, name="AnyIO socket selector")
20
+ self._selector = DefaultSelector()
21
+ self._send, self._receive = socket.socketpair()
22
+ self._send.setblocking(False)
23
+ self._receive.setblocking(False)
24
+ # This somewhat reduces the amount of memory wasted queueing up data
25
+ # for wakeups. With these settings, maximum number of 1-byte sends
26
+ # before getting BlockingIOError:
27
+ # Linux 4.8: 6
28
+ # macOS (darwin 15.5): 1
29
+ # Windows 10: 525347
30
+ # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send
31
+ # blocking, even on non-blocking sockets, so don't do that.)
32
+ self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1)
33
+ self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1)
34
+ # On Windows this is a TCP socket so this might matter. On other
35
+ # platforms this fails b/c AF_UNIX sockets aren't actually TCP.
36
+ try:
37
+ self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
38
+ except OSError:
39
+ pass
40
+
41
+ self._selector.register(self._receive, EVENT_READ)
42
+ self._closed = False
43
+
44
+ def start(self) -> None:
45
+ self._thread.start()
46
+ threading._register_atexit(self._stop) # type: ignore[attr-defined]
47
+
48
+ def _stop(self) -> None:
49
+ global _selector
50
+ self._closed = True
51
+ self._notify_self()
52
+ self._send.close()
53
+ self._thread.join()
54
+ self._selector.unregister(self._receive)
55
+ self._receive.close()
56
+ self._selector.close()
57
+ _selector = None
58
+ assert not self._selector.get_map(), (
59
+ "selector still has registered file descriptors after shutdown"
60
+ )
61
+
62
+ def _notify_self(self) -> None:
63
+ try:
64
+ self._send.send(b"\x00")
65
+ except BlockingIOError:
66
+ pass
67
+
68
+ def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
69
+ loop = asyncio.get_running_loop()
70
+ try:
71
+ key = self._selector.get_key(fd)
72
+ except KeyError:
73
+ self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)})
74
+ else:
75
+ if EVENT_READ in key.data:
76
+ raise ValueError(
77
+ "this file descriptor is already registered for reading"
78
+ )
79
+
80
+ key.data[EVENT_READ] = loop, callback
81
+ self._selector.modify(fd, key.events | EVENT_READ, key.data)
82
+
83
+ self._notify_self()
84
+
85
+ def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
86
+ loop = asyncio.get_running_loop()
87
+ try:
88
+ key = self._selector.get_key(fd)
89
+ except KeyError:
90
+ self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)})
91
+ else:
92
+ if EVENT_WRITE in key.data:
93
+ raise ValueError(
94
+ "this file descriptor is already registered for writing"
95
+ )
96
+
97
+ key.data[EVENT_WRITE] = loop, callback
98
+ self._selector.modify(fd, key.events | EVENT_WRITE, key.data)
99
+
100
+ self._notify_self()
101
+
102
+ def remove_reader(self, fd: FileDescriptorLike) -> bool:
103
+ try:
104
+ key = self._selector.get_key(fd)
105
+ except KeyError:
106
+ return False
107
+
108
+ if new_events := key.events ^ EVENT_READ:
109
+ del key.data[EVENT_READ]
110
+ self._selector.modify(fd, new_events, key.data)
111
+ else:
112
+ self._selector.unregister(fd)
113
+
114
+ return True
115
+
116
+ def remove_writer(self, fd: FileDescriptorLike) -> bool:
117
+ try:
118
+ key = self._selector.get_key(fd)
119
+ except KeyError:
120
+ return False
121
+
122
+ if new_events := key.events ^ EVENT_WRITE:
123
+ del key.data[EVENT_WRITE]
124
+ self._selector.modify(fd, new_events, key.data)
125
+ else:
126
+ self._selector.unregister(fd)
127
+
128
+ return True
129
+
130
+ def run(self) -> None:
131
+ while not self._closed:
132
+ for key, events in self._selector.select():
133
+ if key.fileobj is self._receive:
134
+ try:
135
+ while self._receive.recv(4096):
136
+ pass
137
+ except BlockingIOError:
138
+ pass
139
+
140
+ continue
141
+
142
+ if events & EVENT_READ:
143
+ loop, callback = key.data[EVENT_READ]
144
+ self.remove_reader(key.fd)
145
+ try:
146
+ loop.call_soon_threadsafe(callback)
147
+ except RuntimeError:
148
+ pass # the loop was already closed
149
+
150
+ if events & EVENT_WRITE:
151
+ loop, callback = key.data[EVENT_WRITE]
152
+ self.remove_writer(key.fd)
153
+ try:
154
+ loop.call_soon_threadsafe(callback)
155
+ except RuntimeError:
156
+ pass # the loop was already closed
157
+
158
+
159
+ def get_selector() -> Selector:
160
+ global _selector
161
+
162
+ with _selector_lock:
163
+ if _selector is None:
164
+ _selector = Selector()
165
+ _selector.start()
166
+
167
+ return _selector
backend/.venv/Lib/site-packages/anyio/_core/_contextmanagers.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
5
+ from inspect import isasyncgen, iscoroutine, isgenerator
6
+ from types import TracebackType
7
+ from typing import Protocol, TypeVar, cast, final
8
+
9
+ _T_co = TypeVar("_T_co", covariant=True)
10
+ _ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None")
11
+
12
+
13
+ class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]):
14
+ def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ...
15
+
16
+
17
+ class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]):
18
+ def __asynccontextmanager__(
19
+ self,
20
+ ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ...
21
+
22
+
23
+ class ContextManagerMixin:
24
+ """
25
+ Mixin class providing context manager functionality via a generator-based
26
+ implementation.
27
+
28
+ This class allows you to implement a context manager via :meth:`__contextmanager__`
29
+ which should return a generator. The mechanics are meant to mirror those of
30
+ :func:`@contextmanager <contextlib.contextmanager>`.
31
+
32
+ .. note:: Classes using this mix-in are not reentrant as context managers, meaning
33
+ that once you enter it, you can't re-enter before first exiting it.
34
+
35
+ .. seealso:: :doc:`contextmanagers`
36
+ """
37
+
38
+ __cm: AbstractContextManager[object, bool | None] | None = None
39
+
40
+ @final
41
+ def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co:
42
+ # Needed for mypy to assume self still has the __cm member
43
+ assert isinstance(self, ContextManagerMixin)
44
+ if self.__cm is not None:
45
+ raise RuntimeError(
46
+ f"this {self.__class__.__qualname__} has already been entered"
47
+ )
48
+
49
+ cm = self.__contextmanager__()
50
+ if not isinstance(cm, AbstractContextManager):
51
+ if isgenerator(cm):
52
+ raise TypeError(
53
+ "__contextmanager__() returned a generator object instead of "
54
+ "a context manager. Did you forget to add the @contextmanager "
55
+ "decorator?"
56
+ )
57
+
58
+ raise TypeError(
59
+ f"__contextmanager__() did not return a context manager object, "
60
+ f"but {cm.__class__!r}"
61
+ )
62
+
63
+ if cm is self:
64
+ raise TypeError(
65
+ f"{self.__class__.__qualname__}.__contextmanager__() returned "
66
+ f"self. Did you forget to add the @contextmanager decorator and a "
67
+ f"'yield' statement?"
68
+ )
69
+
70
+ value = cm.__enter__()
71
+ self.__cm = cm
72
+ return value
73
+
74
+ @final
75
+ def __exit__(
76
+ self: _SupportsCtxMgr[object, _ExitT_co],
77
+ exc_type: type[BaseException] | None,
78
+ exc_val: BaseException | None,
79
+ exc_tb: TracebackType | None,
80
+ ) -> _ExitT_co:
81
+ # Needed for mypy to assume self still has the __cm member
82
+ assert isinstance(self, ContextManagerMixin)
83
+ if self.__cm is None:
84
+ raise RuntimeError(
85
+ f"this {self.__class__.__qualname__} has not been entered yet"
86
+ )
87
+
88
+ # Prevent circular references
89
+ cm = self.__cm
90
+ del self.__cm
91
+
92
+ return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb))
93
+
94
+ @abstractmethod
95
+ def __contextmanager__(self) -> AbstractContextManager[object, bool | None]:
96
+ """
97
+ Implement your context manager logic here.
98
+
99
+ This method **must** be decorated with
100
+ :func:`@contextmanager <contextlib.contextmanager>`.
101
+
102
+ .. note:: Remember that the ``yield`` will raise any exception raised in the
103
+ enclosed context block, so use a ``finally:`` block to clean up resources!
104
+
105
+ :return: a context manager object
106
+ """
107
+
108
+
109
+ class AsyncContextManagerMixin:
110
+ """
111
+ Mixin class providing async context manager functionality via a generator-based
112
+ implementation.
113
+
114
+ This class allows you to implement a context manager via
115
+ :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of
116
+ :func:`@asynccontextmanager <contextlib.asynccontextmanager>`.
117
+
118
+ .. note:: Classes using this mix-in are not reentrant as context managers, meaning
119
+ that once you enter it, you can't re-enter before first exiting it.
120
+
121
+ .. seealso:: :doc:`contextmanagers`
122
+ """
123
+
124
+ __cm: AbstractAsyncContextManager[object, bool | None] | None = None
125
+
126
+ @final
127
+ async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co:
128
+ # Needed for mypy to assume self still has the __cm member
129
+ assert isinstance(self, AsyncContextManagerMixin)
130
+ if self.__cm is not None:
131
+ raise RuntimeError(
132
+ f"this {self.__class__.__qualname__} has already been entered"
133
+ )
134
+
135
+ cm = self.__asynccontextmanager__()
136
+ if not isinstance(cm, AbstractAsyncContextManager):
137
+ if isasyncgen(cm):
138
+ raise TypeError(
139
+ "__asynccontextmanager__() returned an async generator instead of "
140
+ "an async context manager. Did you forget to add the "
141
+ "@asynccontextmanager decorator?"
142
+ )
143
+ elif iscoroutine(cm):
144
+ cm.close()
145
+ raise TypeError(
146
+ "__asynccontextmanager__() returned a coroutine object instead of "
147
+ "an async context manager. Did you forget to add the "
148
+ "@asynccontextmanager decorator and a 'yield' statement?"
149
+ )
150
+
151
+ raise TypeError(
152
+ f"__asynccontextmanager__() did not return an async context manager, "
153
+ f"but {cm.__class__!r}"
154
+ )
155
+
156
+ if cm is self:
157
+ raise TypeError(
158
+ f"{self.__class__.__qualname__}.__asynccontextmanager__() returned "
159
+ f"self. Did you forget to add the @asynccontextmanager decorator and a "
160
+ f"'yield' statement?"
161
+ )
162
+
163
+ value = await cm.__aenter__()
164
+ self.__cm = cm
165
+ return value
166
+
167
+ @final
168
+ async def __aexit__(
169
+ self: _SupportsAsyncCtxMgr[object, _ExitT_co],
170
+ exc_type: type[BaseException] | None,
171
+ exc_val: BaseException | None,
172
+ exc_tb: TracebackType | None,
173
+ ) -> _ExitT_co:
174
+ assert isinstance(self, AsyncContextManagerMixin)
175
+ if self.__cm is None:
176
+ raise RuntimeError(
177
+ f"this {self.__class__.__qualname__} has not been entered yet"
178
+ )
179
+
180
+ # Prevent circular references
181
+ cm = self.__cm
182
+ del self.__cm
183
+
184
+ return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb))
185
+
186
+ @abstractmethod
187
+ def __asynccontextmanager__(
188
+ self,
189
+ ) -> AbstractAsyncContextManager[object, bool | None]:
190
+ """
191
+ Implement your async context manager logic here.
192
+
193
+ This method **must** be decorated with
194
+ :func:`@asynccontextmanager <contextlib.asynccontextmanager>`.
195
+
196
+ .. note:: Remember that the ``yield`` will raise any exception raised in the
197
+ enclosed context block, so use a ``finally:`` block to clean up resources!
198
+
199
+ :return: an async context manager object
200
+ """
backend/.venv/Lib/site-packages/anyio/_core/_eventloop.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import sys
5
+ import threading
6
+ from collections.abc import Awaitable, Callable, Generator
7
+ from contextlib import contextmanager
8
+ from contextvars import Token
9
+ from importlib import import_module
10
+ from typing import TYPE_CHECKING, Any, TypeVar
11
+
12
+ if sys.version_info >= (3, 11):
13
+ from typing import TypeVarTuple, Unpack
14
+ else:
15
+ from typing_extensions import TypeVarTuple, Unpack
16
+
17
+ sniffio: Any
18
+ try:
19
+ import sniffio
20
+ except ModuleNotFoundError:
21
+ sniffio = None
22
+
23
+ if TYPE_CHECKING:
24
+ from ..abc import AsyncBackend
25
+
26
+ # This must be updated when new backends are introduced
27
+ BACKENDS = "asyncio", "trio"
28
+
29
+ T_Retval = TypeVar("T_Retval")
30
+ PosArgsT = TypeVarTuple("PosArgsT")
31
+
32
+ threadlocals = threading.local()
33
+ loaded_backends: dict[str, type[AsyncBackend]] = {}
34
+
35
+
36
+ def run(
37
+ func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
38
+ *args: Unpack[PosArgsT],
39
+ backend: str = "asyncio",
40
+ backend_options: dict[str, Any] | None = None,
41
+ ) -> T_Retval:
42
+ """
43
+ Run the given coroutine function in an asynchronous event loop.
44
+
45
+ The current thread must not be already running an event loop.
46
+
47
+ :param func: a coroutine function
48
+ :param args: positional arguments to ``func``
49
+ :param backend: name of the asynchronous event loop implementation – currently
50
+ either ``asyncio`` or ``trio``
51
+ :param backend_options: keyword arguments to call the backend ``run()``
52
+ implementation with (documented :ref:`here <backend options>`)
53
+ :return: the return value of the coroutine function
54
+ :raises RuntimeError: if an asynchronous event loop is already running in this
55
+ thread
56
+ :raises LookupError: if the named backend is not found
57
+
58
+ """
59
+ if asynclib_name := current_async_library():
60
+ raise RuntimeError(f"Already running {asynclib_name} in this thread")
61
+
62
+ try:
63
+ async_backend = get_async_backend(backend)
64
+ except ImportError as exc:
65
+ raise LookupError(f"No such backend: {backend}") from exc
66
+
67
+ token = None
68
+ if asynclib_name is None:
69
+ # Since we're in control of the event loop, we can cache the name of the async
70
+ # library
71
+ token = set_current_async_library(backend)
72
+
73
+ try:
74
+ backend_options = backend_options or {}
75
+ return async_backend.run(func, args, {}, backend_options)
76
+ finally:
77
+ reset_current_async_library(token)
78
+
79
+
80
+ async def sleep(delay: float) -> None:
81
+ """
82
+ Pause the current task for the specified duration.
83
+
84
+ :param delay: the duration, in seconds
85
+
86
+ """
87
+ return await get_async_backend().sleep(delay)
88
+
89
+
90
+ async def sleep_forever() -> None:
91
+ """
92
+ Pause the current task until it's cancelled.
93
+
94
+ This is a shortcut for ``sleep(math.inf)``.
95
+
96
+ .. versionadded:: 3.1
97
+
98
+ """
99
+ await sleep(math.inf)
100
+
101
+
102
+ async def sleep_until(deadline: float) -> None:
103
+ """
104
+ Pause the current task until the given time.
105
+
106
+ :param deadline: the absolute time to wake up at (according to the internal
107
+ monotonic clock of the event loop)
108
+
109
+ .. versionadded:: 3.1
110
+
111
+ """
112
+ now = current_time()
113
+ await sleep(max(deadline - now, 0))
114
+
115
+
116
+ def current_time() -> float:
117
+ """
118
+ Return the current value of the event loop's internal clock.
119
+
120
+ :return: the clock value (seconds)
121
+
122
+ """
123
+ return get_async_backend().current_time()
124
+
125
+
126
+ def get_all_backends() -> tuple[str, ...]:
127
+ """Return a tuple of the names of all built-in backends."""
128
+ return BACKENDS
129
+
130
+
131
+ def get_available_backends() -> tuple[str, ...]:
132
+ """
133
+ Test for the availability of built-in backends.
134
+
135
+ :return a tuple of the built-in backend names that were successfully imported
136
+
137
+ .. versionadded:: 4.12
138
+
139
+ """
140
+ available_backends: list[str] = []
141
+ for backend_name in get_all_backends():
142
+ try:
143
+ get_async_backend(backend_name)
144
+ except ImportError:
145
+ continue
146
+
147
+ available_backends.append(backend_name)
148
+
149
+ return tuple(available_backends)
150
+
151
+
152
+ def get_cancelled_exc_class() -> type[BaseException]:
153
+ """Return the current async library's cancellation exception class."""
154
+ return get_async_backend().cancelled_exception_class()
155
+
156
+
157
+ #
158
+ # Private API
159
+ #
160
+
161
+
162
+ @contextmanager
163
+ def claim_worker_thread(
164
+ backend_class: type[AsyncBackend], token: object
165
+ ) -> Generator[Any, None, None]:
166
+ from ..lowlevel import EventLoopToken
167
+
168
+ threadlocals.current_token = EventLoopToken(backend_class, token)
169
+ try:
170
+ yield
171
+ finally:
172
+ del threadlocals.current_token
173
+
174
+
175
+ class NoCurrentAsyncBackend(Exception):
176
+ def __init__(self) -> None:
177
+ super().__init__(
178
+ f"Not currently running on any asynchronous event loop"
179
+ f"Available async backends: {', '.join(get_all_backends())}"
180
+ )
181
+
182
+
183
+ def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]:
184
+ if asynclib_name is None:
185
+ asynclib_name = current_async_library()
186
+ if not asynclib_name:
187
+ raise NoCurrentAsyncBackend
188
+
189
+ # We use our own dict instead of sys.modules to get the already imported back-end
190
+ # class because the appropriate modules in sys.modules could potentially be only
191
+ # partially initialized
192
+ try:
193
+ return loaded_backends[asynclib_name]
194
+ except KeyError:
195
+ module = import_module(f"anyio._backends._{asynclib_name}")
196
+ loaded_backends[asynclib_name] = module.backend_class
197
+ return module.backend_class
198
+
199
+
200
+ def current_async_library() -> str | None:
201
+ if sniffio is None:
202
+ # If sniffio is not installed, we assume we're either running asyncio or nothing
203
+ import asyncio
204
+
205
+ try:
206
+ asyncio.get_running_loop()
207
+ return "asyncio"
208
+ except RuntimeError:
209
+ pass
210
+ else:
211
+ try:
212
+ return sniffio.current_async_library()
213
+ except sniffio.AsyncLibraryNotFoundError:
214
+ pass
215
+
216
+ return None
217
+
218
+
219
+ def set_current_async_library(asynclib_name: str | None) -> Token | None:
220
+ # no-op if sniffio is not installed
221
+ if sniffio is None:
222
+ return None
223
+
224
+ return sniffio.current_async_library_cvar.set(asynclib_name)
225
+
226
+
227
+ def reset_current_async_library(token: Token | None) -> None:
228
+ if token is not None:
229
+ sniffio.current_async_library_cvar.reset(token)
backend/.venv/Lib/site-packages/anyio/_core/_exceptions.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from collections.abc import Generator
5
+ from textwrap import dedent
6
+ from typing import Any
7
+
8
+ if sys.version_info < (3, 11):
9
+ from exceptiongroup import BaseExceptionGroup
10
+
11
+
12
+ class BrokenResourceError(Exception):
13
+ """
14
+ Raised when trying to use a resource that has been rendered unusable due to external
15
+ causes (e.g. a send stream whose peer has disconnected).
16
+ """
17
+
18
+
19
+ class BrokenWorkerProcess(Exception):
20
+ """
21
+ Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or
22
+ otherwise misbehaves.
23
+ """
24
+
25
+
26
+ class BrokenWorkerInterpreter(Exception):
27
+ """
28
+ Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is
29
+ raised in the subinterpreter.
30
+ """
31
+
32
+ def __init__(self, excinfo: Any):
33
+ # This was adapted from concurrent.futures.interpreter.ExecutionFailed
34
+ msg = excinfo.formatted
35
+ if not msg:
36
+ if excinfo.type and excinfo.msg:
37
+ msg = f"{excinfo.type.__name__}: {excinfo.msg}"
38
+ else:
39
+ msg = excinfo.type.__name__ or excinfo.msg
40
+
41
+ super().__init__(msg)
42
+ self.excinfo = excinfo
43
+
44
+ def __str__(self) -> str:
45
+ try:
46
+ formatted = self.excinfo.errdisplay
47
+ except Exception:
48
+ return super().__str__()
49
+ else:
50
+ return dedent(
51
+ f"""
52
+ {super().__str__()}
53
+
54
+ Uncaught in the interpreter:
55
+
56
+ {formatted}
57
+ """.strip()
58
+ )
59
+
60
+
61
+ class BusyResourceError(Exception):
62
+ """
63
+ Raised when two tasks are trying to read from or write to the same resource
64
+ concurrently.
65
+ """
66
+
67
+ def __init__(self, action: str):
68
+ super().__init__(f"Another task is already {action} this resource")
69
+
70
+
71
+ class ClosedResourceError(Exception):
72
+ """Raised when trying to use a resource that has been closed."""
73
+
74
+
75
+ class ConnectionFailed(OSError):
76
+ """
77
+ Raised when a connection attempt fails.
78
+
79
+ .. note:: This class inherits from :exc:`OSError` for backwards compatibility.
80
+ """
81
+
82
+
83
+ def iterate_exceptions(
84
+ exception: BaseException,
85
+ ) -> Generator[BaseException, None, None]:
86
+ if isinstance(exception, BaseExceptionGroup):
87
+ for exc in exception.exceptions:
88
+ yield from iterate_exceptions(exc)
89
+ else:
90
+ yield exception
91
+
92
+
93
+ class DelimiterNotFound(Exception):
94
+ """
95
+ Raised during
96
+ :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
97
+ maximum number of bytes has been read without the delimiter being found.
98
+ """
99
+
100
+ def __init__(self, max_bytes: int) -> None:
101
+ super().__init__(
102
+ f"The delimiter was not found among the first {max_bytes} bytes"
103
+ )
104
+
105
+
106
+ class EndOfStream(Exception):
107
+ """
108
+ Raised when trying to read from a stream that has been closed from the other end.
109
+ """
110
+
111
+
112
+ class IncompleteRead(Exception):
113
+ """
114
+ Raised during
115
+ :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or
116
+ :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
117
+ connection is closed before the requested amount of bytes has been read.
118
+ """
119
+
120
+ def __init__(self) -> None:
121
+ super().__init__(
122
+ "The stream was closed before the read operation could be completed"
123
+ )
124
+
125
+
126
+ class TypedAttributeLookupError(LookupError):
127
+ """
128
+ Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute
129
+ is not found and no default value has been given.
130
+ """
131
+
132
+
133
+ class WouldBlock(Exception):
134
+ """Raised by ``X_nowait`` functions if ``X()`` would block."""
135
+
136
+
137
+ class NoEventLoopError(RuntimeError):
138
+ """
139
+ Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if
140
+ not calling from an AnyIO worker thread, and no ``token`` was passed.
141
+ """
142
+
143
+
144
+ class RunFinishedError(RuntimeError):
145
+ """
146
+ Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event
147
+ loop associated with the explicitly passed token has already finished.
148
+ """
149
+
150
+ def __init__(self) -> None:
151
+ super().__init__(
152
+ "The event loop associated with the given token has already finished"
153
+ )
backend/.venv/Lib/site-packages/anyio/_core/_fileio.py ADDED
@@ -0,0 +1,797 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import pathlib
5
+ import sys
6
+ from collections.abc import (
7
+ AsyncIterator,
8
+ Callable,
9
+ Iterable,
10
+ Iterator,
11
+ Sequence,
12
+ )
13
+ from dataclasses import dataclass
14
+ from functools import partial
15
+ from os import PathLike
16
+ from typing import (
17
+ IO,
18
+ TYPE_CHECKING,
19
+ Any,
20
+ AnyStr,
21
+ ClassVar,
22
+ Final,
23
+ Generic,
24
+ overload,
25
+ )
26
+
27
+ from .. import to_thread
28
+ from ..abc import AsyncResource
29
+
30
+ if TYPE_CHECKING:
31
+ from types import ModuleType
32
+
33
+ from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
34
+ else:
35
+ ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
36
+
37
+
38
+ class AsyncFile(AsyncResource, Generic[AnyStr]):
39
+ """
40
+ An asynchronous file object.
41
+
42
+ This class wraps a standard file object and provides async friendly versions of the
43
+ following blocking methods (where available on the original file object):
44
+
45
+ * read
46
+ * read1
47
+ * readline
48
+ * readlines
49
+ * readinto
50
+ * readinto1
51
+ * write
52
+ * writelines
53
+ * truncate
54
+ * seek
55
+ * tell
56
+ * flush
57
+
58
+ All other methods are directly passed through.
59
+
60
+ This class supports the asynchronous context manager protocol which closes the
61
+ underlying file at the end of the context block.
62
+
63
+ This class also supports asynchronous iteration::
64
+
65
+ async with await open_file(...) as f:
66
+ async for line in f:
67
+ print(line)
68
+ """
69
+
70
+ def __init__(self, fp: IO[AnyStr]) -> None:
71
+ self._fp: Any = fp
72
+
73
+ def __getattr__(self, name: str) -> object:
74
+ return getattr(self._fp, name)
75
+
76
+ @property
77
+ def wrapped(self) -> IO[AnyStr]:
78
+ """The wrapped file object."""
79
+ return self._fp
80
+
81
+ async def __aiter__(self) -> AsyncIterator[AnyStr]:
82
+ while True:
83
+ line = await self.readline()
84
+ if line:
85
+ yield line
86
+ else:
87
+ break
88
+
89
+ async def aclose(self) -> None:
90
+ return await to_thread.run_sync(self._fp.close)
91
+
92
+ async def read(self, size: int = -1) -> AnyStr:
93
+ return await to_thread.run_sync(self._fp.read, size)
94
+
95
+ async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
96
+ return await to_thread.run_sync(self._fp.read1, size)
97
+
98
+ async def readline(self) -> AnyStr:
99
+ return await to_thread.run_sync(self._fp.readline)
100
+
101
+ async def readlines(self) -> list[AnyStr]:
102
+ return await to_thread.run_sync(self._fp.readlines)
103
+
104
+ async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
105
+ return await to_thread.run_sync(self._fp.readinto, b)
106
+
107
+ async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
108
+ return await to_thread.run_sync(self._fp.readinto1, b)
109
+
110
+ @overload
111
+ async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
112
+
113
+ @overload
114
+ async def write(self: AsyncFile[str], b: str) -> int: ...
115
+
116
+ async def write(self, b: ReadableBuffer | str) -> int:
117
+ return await to_thread.run_sync(self._fp.write, b)
118
+
119
+ @overload
120
+ async def writelines(
121
+ self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
122
+ ) -> None: ...
123
+
124
+ @overload
125
+ async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
126
+
127
+ async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
128
+ return await to_thread.run_sync(self._fp.writelines, lines)
129
+
130
+ async def truncate(self, size: int | None = None) -> int:
131
+ return await to_thread.run_sync(self._fp.truncate, size)
132
+
133
+ async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
134
+ return await to_thread.run_sync(self._fp.seek, offset, whence)
135
+
136
+ async def tell(self) -> int:
137
+ return await to_thread.run_sync(self._fp.tell)
138
+
139
+ async def flush(self) -> None:
140
+ return await to_thread.run_sync(self._fp.flush)
141
+
142
+
143
+ @overload
144
+ async def open_file(
145
+ file: str | PathLike[str] | int,
146
+ mode: OpenBinaryMode,
147
+ buffering: int = ...,
148
+ encoding: str | None = ...,
149
+ errors: str | None = ...,
150
+ newline: str | None = ...,
151
+ closefd: bool = ...,
152
+ opener: Callable[[str, int], int] | None = ...,
153
+ ) -> AsyncFile[bytes]: ...
154
+
155
+
156
+ @overload
157
+ async def open_file(
158
+ file: str | PathLike[str] | int,
159
+ mode: OpenTextMode = ...,
160
+ buffering: int = ...,
161
+ encoding: str | None = ...,
162
+ errors: str | None = ...,
163
+ newline: str | None = ...,
164
+ closefd: bool = ...,
165
+ opener: Callable[[str, int], int] | None = ...,
166
+ ) -> AsyncFile[str]: ...
167
+
168
+
169
+ async def open_file(
170
+ file: str | PathLike[str] | int,
171
+ mode: str = "r",
172
+ buffering: int = -1,
173
+ encoding: str | None = None,
174
+ errors: str | None = None,
175
+ newline: str | None = None,
176
+ closefd: bool = True,
177
+ opener: Callable[[str, int], int] | None = None,
178
+ ) -> AsyncFile[Any]:
179
+ """
180
+ Open a file asynchronously.
181
+
182
+ The arguments are exactly the same as for the builtin :func:`open`.
183
+
184
+ :return: an asynchronous file object
185
+
186
+ """
187
+ fp = await to_thread.run_sync(
188
+ open, file, mode, buffering, encoding, errors, newline, closefd, opener
189
+ )
190
+ return AsyncFile(fp)
191
+
192
+
193
+ def wrap_file(file: IO[AnyStr]) -> AsyncFile[AnyStr]:
194
+ """
195
+ Wrap an existing file as an asynchronous file.
196
+
197
+ :param file: an existing file-like object
198
+ :return: an asynchronous file object
199
+
200
+ """
201
+ return AsyncFile(file)
202
+
203
+
204
+ @dataclass(eq=False)
205
+ class _PathIterator(AsyncIterator["Path"]):
206
+ iterator: Iterator[PathLike[str]]
207
+
208
+ async def __anext__(self) -> Path:
209
+ nextval = await to_thread.run_sync(
210
+ next, self.iterator, None, abandon_on_cancel=True
211
+ )
212
+ if nextval is None:
213
+ raise StopAsyncIteration from None
214
+
215
+ return Path(nextval)
216
+
217
+
218
+ class Path:
219
+ """
220
+ An asynchronous version of :class:`pathlib.Path`.
221
+
222
+ This class cannot be substituted for :class:`pathlib.Path` or
223
+ :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
224
+ interface.
225
+
226
+ It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
227
+ the deprecated :meth:`~pathlib.Path.link_to` method.
228
+
229
+ Some methods may be unavailable or have limited functionality, based on the Python
230
+ version:
231
+
232
+ * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
233
+ * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
234
+ * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
235
+ * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
236
+ * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
237
+ * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
238
+ * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
239
+ available on Python 3.13 or later)
240
+ * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
241
+ * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
242
+ * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
243
+ on Python 3.12 or later)
244
+ * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
245
+
246
+ Any methods that do disk I/O need to be awaited on. These methods are:
247
+
248
+ * :meth:`~pathlib.Path.absolute`
249
+ * :meth:`~pathlib.Path.chmod`
250
+ * :meth:`~pathlib.Path.cwd`
251
+ * :meth:`~pathlib.Path.exists`
252
+ * :meth:`~pathlib.Path.expanduser`
253
+ * :meth:`~pathlib.Path.group`
254
+ * :meth:`~pathlib.Path.hardlink_to`
255
+ * :meth:`~pathlib.Path.home`
256
+ * :meth:`~pathlib.Path.is_block_device`
257
+ * :meth:`~pathlib.Path.is_char_device`
258
+ * :meth:`~pathlib.Path.is_dir`
259
+ * :meth:`~pathlib.Path.is_fifo`
260
+ * :meth:`~pathlib.Path.is_file`
261
+ * :meth:`~pathlib.Path.is_junction`
262
+ * :meth:`~pathlib.Path.is_mount`
263
+ * :meth:`~pathlib.Path.is_socket`
264
+ * :meth:`~pathlib.Path.is_symlink`
265
+ * :meth:`~pathlib.Path.lchmod`
266
+ * :meth:`~pathlib.Path.lstat`
267
+ * :meth:`~pathlib.Path.mkdir`
268
+ * :meth:`~pathlib.Path.open`
269
+ * :meth:`~pathlib.Path.owner`
270
+ * :meth:`~pathlib.Path.read_bytes`
271
+ * :meth:`~pathlib.Path.read_text`
272
+ * :meth:`~pathlib.Path.readlink`
273
+ * :meth:`~pathlib.Path.rename`
274
+ * :meth:`~pathlib.Path.replace`
275
+ * :meth:`~pathlib.Path.resolve`
276
+ * :meth:`~pathlib.Path.rmdir`
277
+ * :meth:`~pathlib.Path.samefile`
278
+ * :meth:`~pathlib.Path.stat`
279
+ * :meth:`~pathlib.Path.symlink_to`
280
+ * :meth:`~pathlib.Path.touch`
281
+ * :meth:`~pathlib.Path.unlink`
282
+ * :meth:`~pathlib.Path.walk`
283
+ * :meth:`~pathlib.Path.write_bytes`
284
+ * :meth:`~pathlib.Path.write_text`
285
+
286
+ Additionally, the following methods return an async iterator yielding
287
+ :class:`~.Path` objects:
288
+
289
+ * :meth:`~pathlib.Path.glob`
290
+ * :meth:`~pathlib.Path.iterdir`
291
+ * :meth:`~pathlib.Path.rglob`
292
+ """
293
+
294
+ __slots__ = "_path", "__weakref__"
295
+
296
+ __weakref__: Any
297
+
298
+ def __init__(self, *args: str | PathLike[str]) -> None:
299
+ self._path: Final[pathlib.Path] = pathlib.Path(*args)
300
+
301
+ def __fspath__(self) -> str:
302
+ return self._path.__fspath__()
303
+
304
+ def __str__(self) -> str:
305
+ return self._path.__str__()
306
+
307
+ def __repr__(self) -> str:
308
+ return f"{self.__class__.__name__}({self.as_posix()!r})"
309
+
310
+ def __bytes__(self) -> bytes:
311
+ return self._path.__bytes__()
312
+
313
+ def __hash__(self) -> int:
314
+ return self._path.__hash__()
315
+
316
+ def __eq__(self, other: object) -> bool:
317
+ target = other._path if isinstance(other, Path) else other
318
+ return self._path.__eq__(target)
319
+
320
+ def __lt__(self, other: pathlib.PurePath | Path) -> bool:
321
+ target = other._path if isinstance(other, Path) else other
322
+ return self._path.__lt__(target)
323
+
324
+ def __le__(self, other: pathlib.PurePath | Path) -> bool:
325
+ target = other._path if isinstance(other, Path) else other
326
+ return self._path.__le__(target)
327
+
328
+ def __gt__(self, other: pathlib.PurePath | Path) -> bool:
329
+ target = other._path if isinstance(other, Path) else other
330
+ return self._path.__gt__(target)
331
+
332
+ def __ge__(self, other: pathlib.PurePath | Path) -> bool:
333
+ target = other._path if isinstance(other, Path) else other
334
+ return self._path.__ge__(target)
335
+
336
+ def __truediv__(self, other: str | PathLike[str]) -> Path:
337
+ return Path(self._path / other)
338
+
339
+ def __rtruediv__(self, other: str | PathLike[str]) -> Path:
340
+ return Path(other) / self
341
+
342
+ @property
343
+ def parts(self) -> tuple[str, ...]:
344
+ return self._path.parts
345
+
346
+ @property
347
+ def drive(self) -> str:
348
+ return self._path.drive
349
+
350
+ @property
351
+ def root(self) -> str:
352
+ return self._path.root
353
+
354
+ @property
355
+ def anchor(self) -> str:
356
+ return self._path.anchor
357
+
358
+ @property
359
+ def parents(self) -> Sequence[Path]:
360
+ return tuple(Path(p) for p in self._path.parents)
361
+
362
+ @property
363
+ def parent(self) -> Path:
364
+ return Path(self._path.parent)
365
+
366
+ @property
367
+ def name(self) -> str:
368
+ return self._path.name
369
+
370
+ @property
371
+ def suffix(self) -> str:
372
+ return self._path.suffix
373
+
374
+ @property
375
+ def suffixes(self) -> list[str]:
376
+ return self._path.suffixes
377
+
378
+ @property
379
+ def stem(self) -> str:
380
+ return self._path.stem
381
+
382
+ async def absolute(self) -> Path:
383
+ path = await to_thread.run_sync(self._path.absolute)
384
+ return Path(path)
385
+
386
+ def as_posix(self) -> str:
387
+ return self._path.as_posix()
388
+
389
+ def as_uri(self) -> str:
390
+ return self._path.as_uri()
391
+
392
+ if sys.version_info >= (3, 13):
393
+ parser: ClassVar[ModuleType] = pathlib.Path.parser
394
+
395
+ @classmethod
396
+ def from_uri(cls, uri: str) -> Path:
397
+ return Path(pathlib.Path.from_uri(uri))
398
+
399
+ def full_match(
400
+ self, path_pattern: str, *, case_sensitive: bool | None = None
401
+ ) -> bool:
402
+ return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
403
+
404
+ def match(
405
+ self, path_pattern: str, *, case_sensitive: bool | None = None
406
+ ) -> bool:
407
+ return self._path.match(path_pattern, case_sensitive=case_sensitive)
408
+ else:
409
+
410
+ def match(self, path_pattern: str) -> bool:
411
+ return self._path.match(path_pattern)
412
+
413
+ if sys.version_info >= (3, 14):
414
+
415
+ @property
416
+ def info(self) -> Any: # TODO: add return type annotation when Typeshed gets it
417
+ return self._path.info
418
+
419
+ async def copy(
420
+ self,
421
+ target: str | os.PathLike[str],
422
+ *,
423
+ follow_symlinks: bool = True,
424
+ preserve_metadata: bool = False,
425
+ ) -> Path:
426
+ func = partial(
427
+ self._path.copy,
428
+ follow_symlinks=follow_symlinks,
429
+ preserve_metadata=preserve_metadata,
430
+ )
431
+ return Path(await to_thread.run_sync(func, pathlib.Path(target)))
432
+
433
+ async def copy_into(
434
+ self,
435
+ target_dir: str | os.PathLike[str],
436
+ *,
437
+ follow_symlinks: bool = True,
438
+ preserve_metadata: bool = False,
439
+ ) -> Path:
440
+ func = partial(
441
+ self._path.copy_into,
442
+ follow_symlinks=follow_symlinks,
443
+ preserve_metadata=preserve_metadata,
444
+ )
445
+ return Path(await to_thread.run_sync(func, pathlib.Path(target_dir)))
446
+
447
+ async def move(self, target: str | os.PathLike[str]) -> Path:
448
+ # Upstream does not handle anyio.Path properly as a PathLike
449
+ target = pathlib.Path(target)
450
+ return Path(await to_thread.run_sync(self._path.move, target))
451
+
452
+ async def move_into(
453
+ self,
454
+ target_dir: str | os.PathLike[str],
455
+ ) -> Path:
456
+ return Path(await to_thread.run_sync(self._path.move_into, target_dir))
457
+
458
+ def is_relative_to(self, other: str | PathLike[str]) -> bool:
459
+ try:
460
+ self.relative_to(other)
461
+ return True
462
+ except ValueError:
463
+ return False
464
+
465
+ async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
466
+ func = partial(os.chmod, follow_symlinks=follow_symlinks)
467
+ return await to_thread.run_sync(func, self._path, mode)
468
+
469
+ @classmethod
470
+ async def cwd(cls) -> Path:
471
+ path = await to_thread.run_sync(pathlib.Path.cwd)
472
+ return cls(path)
473
+
474
+ async def exists(self) -> bool:
475
+ return await to_thread.run_sync(self._path.exists, abandon_on_cancel=True)
476
+
477
+ async def expanduser(self) -> Path:
478
+ return Path(
479
+ await to_thread.run_sync(self._path.expanduser, abandon_on_cancel=True)
480
+ )
481
+
482
+ if sys.version_info < (3, 12):
483
+ # Python 3.11 and earlier
484
+ def glob(self, pattern: str) -> AsyncIterator[Path]:
485
+ gen = self._path.glob(pattern)
486
+ return _PathIterator(gen)
487
+ elif (3, 12) <= sys.version_info < (3, 13):
488
+ # changed in Python 3.12:
489
+ # - The case_sensitive parameter was added.
490
+ def glob(
491
+ self,
492
+ pattern: str,
493
+ *,
494
+ case_sensitive: bool | None = None,
495
+ ) -> AsyncIterator[Path]:
496
+ gen = self._path.glob(pattern, case_sensitive=case_sensitive)
497
+ return _PathIterator(gen)
498
+ elif sys.version_info >= (3, 13):
499
+ # Changed in Python 3.13:
500
+ # - The recurse_symlinks parameter was added.
501
+ # - The pattern parameter accepts a path-like object.
502
+ def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
503
+ self,
504
+ pattern: str | PathLike[str],
505
+ *,
506
+ case_sensitive: bool | None = None,
507
+ recurse_symlinks: bool = False,
508
+ ) -> AsyncIterator[Path]:
509
+ gen = self._path.glob(
510
+ pattern, # type: ignore[arg-type]
511
+ case_sensitive=case_sensitive,
512
+ recurse_symlinks=recurse_symlinks,
513
+ )
514
+ return _PathIterator(gen)
515
+
516
+ async def group(self) -> str:
517
+ return await to_thread.run_sync(self._path.group, abandon_on_cancel=True)
518
+
519
+ async def hardlink_to(
520
+ self, target: str | bytes | PathLike[str] | PathLike[bytes]
521
+ ) -> None:
522
+ if isinstance(target, Path):
523
+ target = target._path
524
+
525
+ await to_thread.run_sync(os.link, target, self)
526
+
527
+ @classmethod
528
+ async def home(cls) -> Path:
529
+ home_path = await to_thread.run_sync(pathlib.Path.home)
530
+ return cls(home_path)
531
+
532
+ def is_absolute(self) -> bool:
533
+ return self._path.is_absolute()
534
+
535
+ async def is_block_device(self) -> bool:
536
+ return await to_thread.run_sync(
537
+ self._path.is_block_device, abandon_on_cancel=True
538
+ )
539
+
540
+ async def is_char_device(self) -> bool:
541
+ return await to_thread.run_sync(
542
+ self._path.is_char_device, abandon_on_cancel=True
543
+ )
544
+
545
+ async def is_dir(self) -> bool:
546
+ return await to_thread.run_sync(self._path.is_dir, abandon_on_cancel=True)
547
+
548
+ async def is_fifo(self) -> bool:
549
+ return await to_thread.run_sync(self._path.is_fifo, abandon_on_cancel=True)
550
+
551
+ async def is_file(self) -> bool:
552
+ return await to_thread.run_sync(self._path.is_file, abandon_on_cancel=True)
553
+
554
+ if sys.version_info >= (3, 12):
555
+
556
+ async def is_junction(self) -> bool:
557
+ return await to_thread.run_sync(self._path.is_junction)
558
+
559
+ async def is_mount(self) -> bool:
560
+ return await to_thread.run_sync(
561
+ os.path.ismount, self._path, abandon_on_cancel=True
562
+ )
563
+
564
+ def is_reserved(self) -> bool:
565
+ return self._path.is_reserved()
566
+
567
+ async def is_socket(self) -> bool:
568
+ return await to_thread.run_sync(self._path.is_socket, abandon_on_cancel=True)
569
+
570
+ async def is_symlink(self) -> bool:
571
+ return await to_thread.run_sync(self._path.is_symlink, abandon_on_cancel=True)
572
+
573
+ async def iterdir(self) -> AsyncIterator[Path]:
574
+ gen = (
575
+ self._path.iterdir()
576
+ if sys.version_info < (3, 13)
577
+ else await to_thread.run_sync(self._path.iterdir, abandon_on_cancel=True)
578
+ )
579
+ async for path in _PathIterator(gen):
580
+ yield path
581
+
582
+ def joinpath(self, *args: str | PathLike[str]) -> Path:
583
+ return Path(self._path.joinpath(*args))
584
+
585
+ async def lchmod(self, mode: int) -> None:
586
+ await to_thread.run_sync(self._path.lchmod, mode)
587
+
588
+ async def lstat(self) -> os.stat_result:
589
+ return await to_thread.run_sync(self._path.lstat, abandon_on_cancel=True)
590
+
591
+ async def mkdir(
592
+ self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
593
+ ) -> None:
594
+ await to_thread.run_sync(self._path.mkdir, mode, parents, exist_ok)
595
+
596
+ @overload
597
+ async def open(
598
+ self,
599
+ mode: OpenBinaryMode,
600
+ buffering: int = ...,
601
+ encoding: str | None = ...,
602
+ errors: str | None = ...,
603
+ newline: str | None = ...,
604
+ ) -> AsyncFile[bytes]: ...
605
+
606
+ @overload
607
+ async def open(
608
+ self,
609
+ mode: OpenTextMode = ...,
610
+ buffering: int = ...,
611
+ encoding: str | None = ...,
612
+ errors: str | None = ...,
613
+ newline: str | None = ...,
614
+ ) -> AsyncFile[str]: ...
615
+
616
+ async def open(
617
+ self,
618
+ mode: str = "r",
619
+ buffering: int = -1,
620
+ encoding: str | None = None,
621
+ errors: str | None = None,
622
+ newline: str | None = None,
623
+ ) -> AsyncFile[Any]:
624
+ fp = await to_thread.run_sync(
625
+ self._path.open, mode, buffering, encoding, errors, newline
626
+ )
627
+ return AsyncFile(fp)
628
+
629
+ async def owner(self) -> str:
630
+ return await to_thread.run_sync(self._path.owner, abandon_on_cancel=True)
631
+
632
+ async def read_bytes(self) -> bytes:
633
+ return await to_thread.run_sync(self._path.read_bytes)
634
+
635
+ async def read_text(
636
+ self, encoding: str | None = None, errors: str | None = None
637
+ ) -> str:
638
+ return await to_thread.run_sync(self._path.read_text, encoding, errors)
639
+
640
+ if sys.version_info >= (3, 12):
641
+
642
+ def relative_to(
643
+ self, *other: str | PathLike[str], walk_up: bool = False
644
+ ) -> Path:
645
+ # relative_to() should work with any PathLike but it doesn't
646
+ others = [pathlib.Path(other) for other in other]
647
+ return Path(self._path.relative_to(*others, walk_up=walk_up))
648
+
649
+ else:
650
+
651
+ def relative_to(self, *other: str | PathLike[str]) -> Path:
652
+ return Path(self._path.relative_to(*other))
653
+
654
+ async def readlink(self) -> Path:
655
+ target = await to_thread.run_sync(os.readlink, self._path)
656
+ return Path(target)
657
+
658
+ async def rename(self, target: str | pathlib.PurePath | Path) -> Path:
659
+ if isinstance(target, Path):
660
+ target = target._path
661
+
662
+ await to_thread.run_sync(self._path.rename, target)
663
+ return Path(target)
664
+
665
+ async def replace(self, target: str | pathlib.PurePath | Path) -> Path:
666
+ if isinstance(target, Path):
667
+ target = target._path
668
+
669
+ await to_thread.run_sync(self._path.replace, target)
670
+ return Path(target)
671
+
672
+ async def resolve(self, strict: bool = False) -> Path:
673
+ func = partial(self._path.resolve, strict=strict)
674
+ return Path(await to_thread.run_sync(func, abandon_on_cancel=True))
675
+
676
+ if sys.version_info < (3, 12):
677
+ # Pre Python 3.12
678
+ def rglob(self, pattern: str) -> AsyncIterator[Path]:
679
+ gen = self._path.rglob(pattern)
680
+ return _PathIterator(gen)
681
+ elif (3, 12) <= sys.version_info < (3, 13):
682
+ # Changed in Python 3.12:
683
+ # - The case_sensitive parameter was added.
684
+ def rglob(
685
+ self, pattern: str, *, case_sensitive: bool | None = None
686
+ ) -> AsyncIterator[Path]:
687
+ gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
688
+ return _PathIterator(gen)
689
+ elif sys.version_info >= (3, 13):
690
+ # Changed in Python 3.13:
691
+ # - The recurse_symlinks parameter was added.
692
+ # - The pattern parameter accepts a path-like object.
693
+ def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
694
+ self,
695
+ pattern: str | PathLike[str],
696
+ *,
697
+ case_sensitive: bool | None = None,
698
+ recurse_symlinks: bool = False,
699
+ ) -> AsyncIterator[Path]:
700
+ gen = self._path.rglob(
701
+ pattern, # type: ignore[arg-type]
702
+ case_sensitive=case_sensitive,
703
+ recurse_symlinks=recurse_symlinks,
704
+ )
705
+ return _PathIterator(gen)
706
+
707
+ async def rmdir(self) -> None:
708
+ await to_thread.run_sync(self._path.rmdir)
709
+
710
+ async def samefile(self, other_path: str | PathLike[str]) -> bool:
711
+ if isinstance(other_path, Path):
712
+ other_path = other_path._path
713
+
714
+ return await to_thread.run_sync(
715
+ self._path.samefile, other_path, abandon_on_cancel=True
716
+ )
717
+
718
+ async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
719
+ func = partial(os.stat, follow_symlinks=follow_symlinks)
720
+ return await to_thread.run_sync(func, self._path, abandon_on_cancel=True)
721
+
722
+ async def symlink_to(
723
+ self,
724
+ target: str | bytes | PathLike[str] | PathLike[bytes],
725
+ target_is_directory: bool = False,
726
+ ) -> None:
727
+ if isinstance(target, Path):
728
+ target = target._path
729
+
730
+ await to_thread.run_sync(self._path.symlink_to, target, target_is_directory)
731
+
732
+ async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
733
+ await to_thread.run_sync(self._path.touch, mode, exist_ok)
734
+
735
+ async def unlink(self, missing_ok: bool = False) -> None:
736
+ try:
737
+ await to_thread.run_sync(self._path.unlink)
738
+ except FileNotFoundError:
739
+ if not missing_ok:
740
+ raise
741
+
742
+ if sys.version_info >= (3, 12):
743
+
744
+ async def walk(
745
+ self,
746
+ top_down: bool = True,
747
+ on_error: Callable[[OSError], object] | None = None,
748
+ follow_symlinks: bool = False,
749
+ ) -> AsyncIterator[tuple[Path, list[str], list[str]]]:
750
+ def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
751
+ try:
752
+ return next(gen)
753
+ except StopIteration:
754
+ return None
755
+
756
+ gen = self._path.walk(top_down, on_error, follow_symlinks)
757
+ while True:
758
+ value = await to_thread.run_sync(get_next_value)
759
+ if value is None:
760
+ return
761
+
762
+ root, dirs, paths = value
763
+ yield Path(root), dirs, paths
764
+
765
+ def with_name(self, name: str) -> Path:
766
+ return Path(self._path.with_name(name))
767
+
768
+ def with_stem(self, stem: str) -> Path:
769
+ return Path(self._path.with_name(stem + self._path.suffix))
770
+
771
+ def with_suffix(self, suffix: str) -> Path:
772
+ return Path(self._path.with_suffix(suffix))
773
+
774
+ def with_segments(self, *pathsegments: str | PathLike[str]) -> Path:
775
+ return Path(*pathsegments)
776
+
777
+ async def write_bytes(self, data: bytes) -> int:
778
+ return await to_thread.run_sync(self._path.write_bytes, data)
779
+
780
+ async def write_text(
781
+ self,
782
+ data: str,
783
+ encoding: str | None = None,
784
+ errors: str | None = None,
785
+ newline: str | None = None,
786
+ ) -> int:
787
+ # Path.write_text() does not support the "newline" parameter before Python 3.10
788
+ def sync_write_text() -> int:
789
+ with self._path.open(
790
+ "w", encoding=encoding, errors=errors, newline=newline
791
+ ) as fp:
792
+ return fp.write(data)
793
+
794
+ return await to_thread.run_sync(sync_write_text)
795
+
796
+
797
+ PathLike.register(Path)
backend/.venv/Lib/site-packages/anyio/_core/_resources.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from ..abc import AsyncResource
4
+ from ._tasks import CancelScope
5
+
6
+
7
+ async def aclose_forcefully(resource: AsyncResource) -> None:
8
+ """
9
+ Close an asynchronous resource in a cancelled scope.
10
+
11
+ Doing this closes the resource without waiting on anything.
12
+
13
+ :param resource: the resource to close
14
+
15
+ """
16
+ with CancelScope() as scope:
17
+ scope.cancel()
18
+ await resource.aclose()
backend/.venv/Lib/site-packages/anyio/_core/_signals.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator
4
+ from contextlib import AbstractContextManager
5
+ from signal import Signals
6
+
7
+ from ._eventloop import get_async_backend
8
+
9
+
10
+ def open_signal_receiver(
11
+ *signals: Signals,
12
+ ) -> AbstractContextManager[AsyncIterator[Signals]]:
13
+ """
14
+ Start receiving operating system signals.
15
+
16
+ :param signals: signals to receive (e.g. ``signal.SIGINT``)
17
+ :return: an asynchronous context manager for an asynchronous iterator which yields
18
+ signal numbers
19
+
20
+ .. warning:: Windows does not support signals natively so it is best to avoid
21
+ relying on this in cross-platform applications.
22
+
23
+ .. warning:: On asyncio, this permanently replaces any previous signal handler for
24
+ the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`.
25
+
26
+ """
27
+ return get_async_backend().open_signal_receiver(*signals)
backend/.venv/Lib/site-packages/anyio/_core/_sockets.py ADDED
@@ -0,0 +1,991 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import errno
4
+ import os
5
+ import socket
6
+ import ssl
7
+ import stat
8
+ import sys
9
+ from collections.abc import Awaitable
10
+ from dataclasses import dataclass
11
+ from ipaddress import IPv4Address, IPv6Address, ip_address
12
+ from os import PathLike, chmod
13
+ from socket import AddressFamily, SocketKind
14
+ from typing import TYPE_CHECKING, Any, Literal, cast, overload
15
+
16
+ from .. import ConnectionFailed, to_thread
17
+ from ..abc import (
18
+ ByteStreamConnectable,
19
+ ConnectedUDPSocket,
20
+ ConnectedUNIXDatagramSocket,
21
+ IPAddressType,
22
+ IPSockAddrType,
23
+ SocketListener,
24
+ SocketStream,
25
+ UDPSocket,
26
+ UNIXDatagramSocket,
27
+ UNIXSocketStream,
28
+ )
29
+ from ..streams.stapled import MultiListener
30
+ from ..streams.tls import TLSConnectable, TLSStream
31
+ from ._eventloop import get_async_backend
32
+ from ._resources import aclose_forcefully
33
+ from ._synchronization import Event
34
+ from ._tasks import create_task_group, move_on_after
35
+
36
+ if TYPE_CHECKING:
37
+ from _typeshed import FileDescriptorLike
38
+ else:
39
+ FileDescriptorLike = object
40
+
41
+ if sys.version_info < (3, 11):
42
+ from exceptiongroup import ExceptionGroup
43
+
44
+ if sys.version_info >= (3, 12):
45
+ from typing import override
46
+ else:
47
+ from typing_extensions import override
48
+
49
+ if sys.version_info < (3, 13):
50
+ from typing_extensions import deprecated
51
+ else:
52
+ from warnings import deprecated
53
+
54
+ IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515
55
+
56
+ AnyIPAddressFamily = Literal[
57
+ AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6
58
+ ]
59
+ IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6]
60
+
61
+
62
+ # tls_hostname given
63
+ @overload
64
+ async def connect_tcp(
65
+ remote_host: IPAddressType,
66
+ remote_port: int,
67
+ *,
68
+ local_host: IPAddressType | None = ...,
69
+ ssl_context: ssl.SSLContext | None = ...,
70
+ tls_standard_compatible: bool = ...,
71
+ tls_hostname: str,
72
+ happy_eyeballs_delay: float = ...,
73
+ ) -> TLSStream: ...
74
+
75
+
76
+ # ssl_context given
77
+ @overload
78
+ async def connect_tcp(
79
+ remote_host: IPAddressType,
80
+ remote_port: int,
81
+ *,
82
+ local_host: IPAddressType | None = ...,
83
+ ssl_context: ssl.SSLContext,
84
+ tls_standard_compatible: bool = ...,
85
+ tls_hostname: str | None = ...,
86
+ happy_eyeballs_delay: float = ...,
87
+ ) -> TLSStream: ...
88
+
89
+
90
+ # tls=True
91
+ @overload
92
+ async def connect_tcp(
93
+ remote_host: IPAddressType,
94
+ remote_port: int,
95
+ *,
96
+ local_host: IPAddressType | None = ...,
97
+ tls: Literal[True],
98
+ ssl_context: ssl.SSLContext | None = ...,
99
+ tls_standard_compatible: bool = ...,
100
+ tls_hostname: str | None = ...,
101
+ happy_eyeballs_delay: float = ...,
102
+ ) -> TLSStream: ...
103
+
104
+
105
+ # tls=False
106
+ @overload
107
+ async def connect_tcp(
108
+ remote_host: IPAddressType,
109
+ remote_port: int,
110
+ *,
111
+ local_host: IPAddressType | None = ...,
112
+ tls: Literal[False],
113
+ ssl_context: ssl.SSLContext | None = ...,
114
+ tls_standard_compatible: bool = ...,
115
+ tls_hostname: str | None = ...,
116
+ happy_eyeballs_delay: float = ...,
117
+ ) -> SocketStream: ...
118
+
119
+
120
+ # No TLS arguments
121
+ @overload
122
+ async def connect_tcp(
123
+ remote_host: IPAddressType,
124
+ remote_port: int,
125
+ *,
126
+ local_host: IPAddressType | None = ...,
127
+ happy_eyeballs_delay: float = ...,
128
+ ) -> SocketStream: ...
129
+
130
+
131
+ async def connect_tcp(
132
+ remote_host: IPAddressType,
133
+ remote_port: int,
134
+ *,
135
+ local_host: IPAddressType | None = None,
136
+ tls: bool = False,
137
+ ssl_context: ssl.SSLContext | None = None,
138
+ tls_standard_compatible: bool = True,
139
+ tls_hostname: str | None = None,
140
+ happy_eyeballs_delay: float = 0.25,
141
+ ) -> SocketStream | TLSStream:
142
+ """
143
+ Connect to a host using the TCP protocol.
144
+
145
+ This function implements the stateless version of the Happy Eyeballs algorithm (RFC
146
+ 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses,
147
+ each one is tried until one connection attempt succeeds. If the first attempt does
148
+ not connected within 250 milliseconds, a second attempt is started using the next
149
+ address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if
150
+ available) is tried first.
151
+
152
+ When the connection has been established, a TLS handshake will be done if either
153
+ ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``.
154
+
155
+ :param remote_host: the IP address or host name to connect to
156
+ :param remote_port: port on the target host to connect to
157
+ :param local_host: the interface address or name to bind the socket to before
158
+ connecting
159
+ :param tls: ``True`` to do a TLS handshake with the connected stream and return a
160
+ :class:`~anyio.streams.tls.TLSStream` instead
161
+ :param ssl_context: the SSL context object to use (if omitted, a default context is
162
+ created)
163
+ :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake
164
+ before closing the stream and requires that the server does this as well.
165
+ Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream.
166
+ Some protocols, such as HTTP, require this option to be ``False``.
167
+ See :meth:`~ssl.SSLContext.wrap_socket` for details.
168
+ :param tls_hostname: host name to check the server certificate against (defaults to
169
+ the value of ``remote_host``)
170
+ :param happy_eyeballs_delay: delay (in seconds) before starting the next connection
171
+ attempt
172
+ :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream
173
+ :raises ConnectionFailed: if the connection fails
174
+
175
+ """
176
+ # Placed here due to https://github.com/python/mypy/issues/7057
177
+ connected_stream: SocketStream | None = None
178
+
179
+ async def try_connect(remote_host: str, event: Event) -> None:
180
+ nonlocal connected_stream
181
+ try:
182
+ stream = await asynclib.connect_tcp(remote_host, remote_port, local_address)
183
+ except OSError as exc:
184
+ oserrors.append(exc)
185
+ return
186
+ else:
187
+ if connected_stream is None:
188
+ connected_stream = stream
189
+ tg.cancel_scope.cancel()
190
+ else:
191
+ await stream.aclose()
192
+ finally:
193
+ event.set()
194
+
195
+ asynclib = get_async_backend()
196
+ local_address: IPSockAddrType | None = None
197
+ family = socket.AF_UNSPEC
198
+ if local_host:
199
+ gai_res = await getaddrinfo(str(local_host), None)
200
+ family, *_, local_address = gai_res[0]
201
+
202
+ target_host = str(remote_host)
203
+ try:
204
+ addr_obj = ip_address(remote_host)
205
+ except ValueError:
206
+ addr_obj = None
207
+
208
+ if addr_obj is not None:
209
+ if isinstance(addr_obj, IPv6Address):
210
+ target_addrs = [(socket.AF_INET6, addr_obj.compressed)]
211
+ else:
212
+ target_addrs = [(socket.AF_INET, addr_obj.compressed)]
213
+ else:
214
+ # getaddrinfo() will raise an exception if name resolution fails
215
+ gai_res = await getaddrinfo(
216
+ target_host, remote_port, family=family, type=socket.SOCK_STREAM
217
+ )
218
+
219
+ # Organize the list so that the first address is an IPv6 address (if available)
220
+ # and the second one is an IPv4 addresses. The rest can be in whatever order.
221
+ v6_found = v4_found = False
222
+ target_addrs = []
223
+ for af, *_, sa in gai_res:
224
+ if af == socket.AF_INET6 and not v6_found:
225
+ v6_found = True
226
+ target_addrs.insert(0, (af, sa[0]))
227
+ elif af == socket.AF_INET and not v4_found and v6_found:
228
+ v4_found = True
229
+ target_addrs.insert(1, (af, sa[0]))
230
+ else:
231
+ target_addrs.append((af, sa[0]))
232
+
233
+ oserrors: list[OSError] = []
234
+ try:
235
+ async with create_task_group() as tg:
236
+ for _af, addr in target_addrs:
237
+ event = Event()
238
+ tg.start_soon(try_connect, addr, event)
239
+ with move_on_after(happy_eyeballs_delay):
240
+ await event.wait()
241
+
242
+ if connected_stream is None:
243
+ cause = (
244
+ oserrors[0]
245
+ if len(oserrors) == 1
246
+ else ExceptionGroup("multiple connection attempts failed", oserrors)
247
+ )
248
+ raise OSError("All connection attempts failed") from cause
249
+ finally:
250
+ oserrors.clear()
251
+
252
+ if tls or tls_hostname or ssl_context:
253
+ try:
254
+ return await TLSStream.wrap(
255
+ connected_stream,
256
+ server_side=False,
257
+ hostname=tls_hostname or str(remote_host),
258
+ ssl_context=ssl_context,
259
+ standard_compatible=tls_standard_compatible,
260
+ )
261
+ except BaseException:
262
+ await aclose_forcefully(connected_stream)
263
+ raise
264
+
265
+ return connected_stream
266
+
267
+
268
+ async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream:
269
+ """
270
+ Connect to the given UNIX socket.
271
+
272
+ Not available on Windows.
273
+
274
+ :param path: path to the socket
275
+ :return: a socket stream object
276
+ :raises ConnectionFailed: if the connection fails
277
+
278
+ """
279
+ path = os.fspath(path)
280
+ return await get_async_backend().connect_unix(path)
281
+
282
+
283
+ async def create_tcp_listener(
284
+ *,
285
+ local_host: IPAddressType | None = None,
286
+ local_port: int = 0,
287
+ family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC,
288
+ backlog: int = 65536,
289
+ reuse_port: bool = False,
290
+ ) -> MultiListener[SocketStream]:
291
+ """
292
+ Create a TCP socket listener.
293
+
294
+ :param local_port: port number to listen on
295
+ :param local_host: IP address of the interface to listen on. If omitted, listen on
296
+ all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address
297
+ family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6.
298
+ :param family: address family (used if ``local_host`` was omitted)
299
+ :param backlog: maximum number of queued incoming connections (up to a maximum of
300
+ 2**16, or 65536)
301
+ :param reuse_port: ``True`` to allow multiple sockets to bind to the same
302
+ address/port (not supported on Windows)
303
+ :return: a multi-listener object containing one or more socket listeners
304
+ :raises OSError: if there's an error creating a socket, or binding to one or more
305
+ interfaces failed
306
+
307
+ """
308
+ asynclib = get_async_backend()
309
+ backlog = min(backlog, 65536)
310
+ local_host = str(local_host) if local_host is not None else None
311
+
312
+ def setup_raw_socket(
313
+ fam: AddressFamily,
314
+ bind_addr: tuple[str, int] | tuple[str, int, int, int],
315
+ *,
316
+ v6only: bool = True,
317
+ ) -> socket.socket:
318
+ sock = socket.socket(fam)
319
+ try:
320
+ sock.setblocking(False)
321
+
322
+ if fam == AddressFamily.AF_INET6:
323
+ sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only)
324
+
325
+ # For Windows, enable exclusive address use. For others, enable address
326
+ # reuse.
327
+ if sys.platform == "win32":
328
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
329
+ else:
330
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
331
+
332
+ if reuse_port:
333
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
334
+
335
+ # Workaround for #554
336
+ if fam == socket.AF_INET6 and "%" in bind_addr[0]:
337
+ addr, scope_id = bind_addr[0].split("%", 1)
338
+ bind_addr = (addr, bind_addr[1], 0, int(scope_id))
339
+
340
+ sock.bind(bind_addr)
341
+ sock.listen(backlog)
342
+ except BaseException:
343
+ sock.close()
344
+ raise
345
+
346
+ return sock
347
+
348
+ # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug
349
+ # where we don't get the correct scope ID for IPv6 link-local addresses when passing
350
+ # type=socket.SOCK_STREAM to getaddrinfo():
351
+ # https://github.com/MagicStack/uvloop/issues/539
352
+ gai_res = await getaddrinfo(
353
+ local_host,
354
+ local_port,
355
+ family=family,
356
+ type=socket.SOCK_STREAM if sys.platform == "win32" else 0,
357
+ flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
358
+ )
359
+
360
+ # The set comprehension is here to work around a glibc bug:
361
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=14969
362
+ sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM})
363
+
364
+ # Special case for dual-stack binding on the "any" interface
365
+ if (
366
+ local_host is None
367
+ and family == AddressFamily.AF_UNSPEC
368
+ and socket.has_dualstack_ipv6()
369
+ and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res)
370
+ ):
371
+ raw_socket = setup_raw_socket(
372
+ AddressFamily.AF_INET6, ("::", local_port), v6only=False
373
+ )
374
+ listener = asynclib.create_tcp_listener(raw_socket)
375
+ return MultiListener([listener])
376
+
377
+ errors: list[OSError] = []
378
+ try:
379
+ for _ in range(len(sockaddrs)):
380
+ listeners: list[SocketListener] = []
381
+ bound_ephemeral_port = local_port
382
+ try:
383
+ for fam, *_, sockaddr in sockaddrs:
384
+ sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:]
385
+ raw_socket = setup_raw_socket(fam, sockaddr)
386
+
387
+ # Store the assigned port if an ephemeral port was requested, so
388
+ # we'll bind to the same port on all interfaces
389
+ if local_port == 0 and len(gai_res) > 1:
390
+ bound_ephemeral_port = raw_socket.getsockname()[1]
391
+
392
+ listeners.append(asynclib.create_tcp_listener(raw_socket))
393
+ except BaseException as exc:
394
+ for listener in listeners:
395
+ await listener.aclose()
396
+
397
+ # If an ephemeral port was requested but binding the assigned port
398
+ # failed for another interface, rotate the address list and try again
399
+ if (
400
+ isinstance(exc, OSError)
401
+ and exc.errno == errno.EADDRINUSE
402
+ and local_port == 0
403
+ and bound_ephemeral_port
404
+ ):
405
+ errors.append(exc)
406
+ sockaddrs.append(sockaddrs.pop(0))
407
+ continue
408
+
409
+ raise
410
+
411
+ return MultiListener(listeners)
412
+
413
+ raise OSError(
414
+ f"Could not create {len(sockaddrs)} listeners with a consistent port"
415
+ ) from ExceptionGroup("Several bind attempts failed", errors)
416
+ finally:
417
+ del errors # Prevent reference cycles
418
+
419
+
420
+ async def create_unix_listener(
421
+ path: str | bytes | PathLike[Any],
422
+ *,
423
+ mode: int | None = None,
424
+ backlog: int = 65536,
425
+ ) -> SocketListener:
426
+ """
427
+ Create a UNIX socket listener.
428
+
429
+ Not available on Windows.
430
+
431
+ :param path: path of the socket
432
+ :param mode: permissions to set on the socket
433
+ :param backlog: maximum number of queued incoming connections (up to a maximum of
434
+ 2**16, or 65536)
435
+ :return: a listener object
436
+
437
+ .. versionchanged:: 3.0
438
+ If a socket already exists on the file system in the given path, it will be
439
+ removed first.
440
+
441
+ """
442
+ backlog = min(backlog, 65536)
443
+ raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM)
444
+ try:
445
+ raw_socket.listen(backlog)
446
+ return get_async_backend().create_unix_listener(raw_socket)
447
+ except BaseException:
448
+ raw_socket.close()
449
+ raise
450
+
451
+
452
+ async def create_udp_socket(
453
+ family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
454
+ *,
455
+ local_host: IPAddressType | None = None,
456
+ local_port: int = 0,
457
+ reuse_port: bool = False,
458
+ ) -> UDPSocket:
459
+ """
460
+ Create a UDP socket.
461
+
462
+ If ``port`` has been given, the socket will be bound to this port on the local
463
+ machine, making this socket suitable for providing UDP based services.
464
+
465
+ :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
466
+ determined from ``local_host`` if omitted
467
+ :param local_host: IP address or host name of the local interface to bind to
468
+ :param local_port: local port to bind to
469
+ :param reuse_port: ``True`` to allow multiple sockets to bind to the same
470
+ address/port (not supported on Windows)
471
+ :return: a UDP socket
472
+
473
+ """
474
+ if family is AddressFamily.AF_UNSPEC and not local_host:
475
+ raise ValueError('Either "family" or "local_host" must be given')
476
+
477
+ if local_host:
478
+ gai_res = await getaddrinfo(
479
+ str(local_host),
480
+ local_port,
481
+ family=family,
482
+ type=socket.SOCK_DGRAM,
483
+ flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
484
+ )
485
+ family = cast(AnyIPAddressFamily, gai_res[0][0])
486
+ local_address = gai_res[0][-1]
487
+ elif family is AddressFamily.AF_INET6:
488
+ local_address = ("::", 0)
489
+ else:
490
+ local_address = ("0.0.0.0", 0)
491
+
492
+ sock = await get_async_backend().create_udp_socket(
493
+ family, local_address, None, reuse_port
494
+ )
495
+ return cast(UDPSocket, sock)
496
+
497
+
498
+ async def create_connected_udp_socket(
499
+ remote_host: IPAddressType,
500
+ remote_port: int,
501
+ *,
502
+ family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
503
+ local_host: IPAddressType | None = None,
504
+ local_port: int = 0,
505
+ reuse_port: bool = False,
506
+ ) -> ConnectedUDPSocket:
507
+ """
508
+ Create a connected UDP socket.
509
+
510
+ Connected UDP sockets can only communicate with the specified remote host/port, an
511
+ any packets sent from other sources are dropped.
512
+
513
+ :param remote_host: remote host to set as the default target
514
+ :param remote_port: port on the remote host to set as the default target
515
+ :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
516
+ determined from ``local_host`` or ``remote_host`` if omitted
517
+ :param local_host: IP address or host name of the local interface to bind to
518
+ :param local_port: local port to bind to
519
+ :param reuse_port: ``True`` to allow multiple sockets to bind to the same
520
+ address/port (not supported on Windows)
521
+ :return: a connected UDP socket
522
+
523
+ """
524
+ local_address = None
525
+ if local_host:
526
+ gai_res = await getaddrinfo(
527
+ str(local_host),
528
+ local_port,
529
+ family=family,
530
+ type=socket.SOCK_DGRAM,
531
+ flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
532
+ )
533
+ family = cast(AnyIPAddressFamily, gai_res[0][0])
534
+ local_address = gai_res[0][-1]
535
+
536
+ gai_res = await getaddrinfo(
537
+ str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM
538
+ )
539
+ family = cast(AnyIPAddressFamily, gai_res[0][0])
540
+ remote_address = gai_res[0][-1]
541
+
542
+ sock = await get_async_backend().create_udp_socket(
543
+ family, local_address, remote_address, reuse_port
544
+ )
545
+ return cast(ConnectedUDPSocket, sock)
546
+
547
+
548
+ async def create_unix_datagram_socket(
549
+ *,
550
+ local_path: None | str | bytes | PathLike[Any] = None,
551
+ local_mode: int | None = None,
552
+ ) -> UNIXDatagramSocket:
553
+ """
554
+ Create a UNIX datagram socket.
555
+
556
+ Not available on Windows.
557
+
558
+ If ``local_path`` has been given, the socket will be bound to this path, making this
559
+ socket suitable for receiving datagrams from other processes. Other processes can
560
+ send datagrams to this socket only if ``local_path`` is set.
561
+
562
+ If a socket already exists on the file system in the ``local_path``, it will be
563
+ removed first.
564
+
565
+ :param local_path: the path on which to bind to
566
+ :param local_mode: permissions to set on the local socket
567
+ :return: a UNIX datagram socket
568
+
569
+ """
570
+ raw_socket = await setup_unix_local_socket(
571
+ local_path, local_mode, socket.SOCK_DGRAM
572
+ )
573
+ return await get_async_backend().create_unix_datagram_socket(raw_socket, None)
574
+
575
+
576
+ async def create_connected_unix_datagram_socket(
577
+ remote_path: str | bytes | PathLike[Any],
578
+ *,
579
+ local_path: None | str | bytes | PathLike[Any] = None,
580
+ local_mode: int | None = None,
581
+ ) -> ConnectedUNIXDatagramSocket:
582
+ """
583
+ Create a connected UNIX datagram socket.
584
+
585
+ Connected datagram sockets can only communicate with the specified remote path.
586
+
587
+ If ``local_path`` has been given, the socket will be bound to this path, making
588
+ this socket suitable for receiving datagrams from other processes. Other processes
589
+ can send datagrams to this socket only if ``local_path`` is set.
590
+
591
+ If a socket already exists on the file system in the ``local_path``, it will be
592
+ removed first.
593
+
594
+ :param remote_path: the path to set as the default target
595
+ :param local_path: the path on which to bind to
596
+ :param local_mode: permissions to set on the local socket
597
+ :return: a connected UNIX datagram socket
598
+
599
+ """
600
+ remote_path = os.fspath(remote_path)
601
+ raw_socket = await setup_unix_local_socket(
602
+ local_path, local_mode, socket.SOCK_DGRAM
603
+ )
604
+ return await get_async_backend().create_unix_datagram_socket(
605
+ raw_socket, remote_path
606
+ )
607
+
608
+
609
+ async def getaddrinfo(
610
+ host: bytes | str | None,
611
+ port: str | int | None,
612
+ *,
613
+ family: int | AddressFamily = 0,
614
+ type: int | SocketKind = 0,
615
+ proto: int = 0,
616
+ flags: int = 0,
617
+ ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]:
618
+ """
619
+ Look up a numeric IP address given a host name.
620
+
621
+ Internationalized domain names are translated according to the (non-transitional)
622
+ IDNA 2008 standard.
623
+
624
+ .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of
625
+ (host, port), unlike what :func:`socket.getaddrinfo` does.
626
+
627
+ :param host: host name
628
+ :param port: port number
629
+ :param family: socket family (`'AF_INET``, ...)
630
+ :param type: socket type (``SOCK_STREAM``, ...)
631
+ :param proto: protocol number
632
+ :param flags: flags to pass to upstream ``getaddrinfo()``
633
+ :return: list of tuples containing (family, type, proto, canonname, sockaddr)
634
+
635
+ .. seealso:: :func:`socket.getaddrinfo`
636
+
637
+ """
638
+ # Handle unicode hostnames
639
+ if isinstance(host, str):
640
+ try:
641
+ encoded_host: bytes | None = host.encode("ascii")
642
+ except UnicodeEncodeError:
643
+ import idna
644
+
645
+ encoded_host = idna.encode(host, uts46=True)
646
+ else:
647
+ encoded_host = host
648
+
649
+ gai_res = await get_async_backend().getaddrinfo(
650
+ encoded_host, port, family=family, type=type, proto=proto, flags=flags
651
+ )
652
+ return [
653
+ (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr))
654
+ for family, type, proto, canonname, sockaddr in gai_res
655
+ # filter out IPv6 results when IPv6 is disabled
656
+ if not isinstance(sockaddr[0], int)
657
+ ]
658
+
659
+
660
+ def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]:
661
+ """
662
+ Look up the host name of an IP address.
663
+
664
+ :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4)
665
+ :param flags: flags to pass to upstream ``getnameinfo()``
666
+ :return: a tuple of (host name, service name)
667
+
668
+ .. seealso:: :func:`socket.getnameinfo`
669
+
670
+ """
671
+ return get_async_backend().getnameinfo(sockaddr, flags)
672
+
673
+
674
+ @deprecated("This function is deprecated; use `wait_readable` instead")
675
+ def wait_socket_readable(sock: socket.socket) -> Awaitable[None]:
676
+ """
677
+ .. deprecated:: 4.7.0
678
+ Use :func:`wait_readable` instead.
679
+
680
+ Wait until the given socket has data to be read.
681
+
682
+ .. warning:: Only use this on raw sockets that have not been wrapped by any higher
683
+ level constructs like socket streams!
684
+
685
+ :param sock: a socket object
686
+ :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
687
+ socket to become readable
688
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
689
+ to become readable
690
+
691
+ """
692
+ return get_async_backend().wait_readable(sock.fileno())
693
+
694
+
695
+ @deprecated("This function is deprecated; use `wait_writable` instead")
696
+ def wait_socket_writable(sock: socket.socket) -> Awaitable[None]:
697
+ """
698
+ .. deprecated:: 4.7.0
699
+ Use :func:`wait_writable` instead.
700
+
701
+ Wait until the given socket can be written to.
702
+
703
+ This does **NOT** work on Windows when using the asyncio backend with a proactor
704
+ event loop (default on py3.8+).
705
+
706
+ .. warning:: Only use this on raw sockets that have not been wrapped by any higher
707
+ level constructs like socket streams!
708
+
709
+ :param sock: a socket object
710
+ :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
711
+ socket to become writable
712
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
713
+ to become writable
714
+
715
+ """
716
+ return get_async_backend().wait_writable(sock.fileno())
717
+
718
+
719
+ def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]:
720
+ """
721
+ Wait until the given object has data to be read.
722
+
723
+ On Unix systems, ``obj`` must either be an integer file descriptor, or else an
724
+ object with a ``.fileno()`` method which returns an integer file descriptor. Any
725
+ kind of file descriptor can be passed, though the exact semantics will depend on
726
+ your kernel. For example, this probably won't do anything useful for on-disk files.
727
+
728
+ On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an
729
+ object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File
730
+ descriptors aren't supported, and neither are handles that refer to anything besides
731
+ a ``SOCKET``.
732
+
733
+ On backends where this functionality is not natively provided (asyncio
734
+ ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread
735
+ which is set to shut down when the interpreter shuts down.
736
+
737
+ .. warning:: Don't use this on raw sockets that have been wrapped by any higher
738
+ level constructs like socket streams!
739
+
740
+ :param obj: an object with a ``.fileno()`` method or an integer handle
741
+ :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
742
+ object to become readable
743
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the object
744
+ to become readable
745
+
746
+ """
747
+ return get_async_backend().wait_readable(obj)
748
+
749
+
750
+ def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]:
751
+ """
752
+ Wait until the given object can be written to.
753
+
754
+ :param obj: an object with a ``.fileno()`` method or an integer handle
755
+ :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
756
+ object to become writable
757
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the object
758
+ to become writable
759
+
760
+ .. seealso:: See the documentation of :func:`wait_readable` for the definition of
761
+ ``obj`` and notes on backend compatibility.
762
+
763
+ .. warning:: Don't use this on raw sockets that have been wrapped by any higher
764
+ level constructs like socket streams!
765
+
766
+ """
767
+ return get_async_backend().wait_writable(obj)
768
+
769
+
770
+ def notify_closing(obj: FileDescriptorLike) -> None:
771
+ """
772
+ Call this before closing a file descriptor (on Unix) or socket (on
773
+ Windows). This will cause any `wait_readable` or `wait_writable`
774
+ calls on the given object to immediately wake up and raise
775
+ `~anyio.ClosedResourceError`.
776
+
777
+ This doesn't actually close the object – you still have to do that
778
+ yourself afterwards. Also, you want to be careful to make sure no
779
+ new tasks start waiting on the object in between when you call this
780
+ and when it's actually closed. So to close something properly, you
781
+ usually want to do these steps in order:
782
+
783
+ 1. Explicitly mark the object as closed, so that any new attempts
784
+ to use it will abort before they start.
785
+ 2. Call `notify_closing` to wake up any already-existing users.
786
+ 3. Actually close the object.
787
+
788
+ It's also possible to do them in a different order if that's more
789
+ convenient, *but only if* you make sure not to have any checkpoints in
790
+ between the steps. This way they all happen in a single atomic
791
+ step, so other tasks won't be able to tell what order they happened
792
+ in anyway.
793
+
794
+ :param obj: an object with a ``.fileno()`` method or an integer handle
795
+
796
+ """
797
+ get_async_backend().notify_closing(obj)
798
+
799
+
800
+ #
801
+ # Private API
802
+ #
803
+
804
+
805
+ def convert_ipv6_sockaddr(
806
+ sockaddr: tuple[str, int, int, int] | tuple[str, int],
807
+ ) -> tuple[str, int]:
808
+ """
809
+ Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format.
810
+
811
+ If the scope ID is nonzero, it is added to the address, separated with ``%``.
812
+ Otherwise the flow id and scope id are simply cut off from the tuple.
813
+ Any other kinds of socket addresses are returned as-is.
814
+
815
+ :param sockaddr: the result of :meth:`~socket.socket.getsockname`
816
+ :return: the converted socket address
817
+
818
+ """
819
+ # This is more complicated than it should be because of MyPy
820
+ if isinstance(sockaddr, tuple) and len(sockaddr) == 4:
821
+ host, port, flowinfo, scope_id = sockaddr
822
+ if scope_id:
823
+ # PyPy (as of v7.3.11) leaves the interface name in the result, so
824
+ # we discard it and only get the scope ID from the end
825
+ # (https://foss.heptapod.net/pypy/pypy/-/issues/3938)
826
+ host = host.split("%")[0]
827
+
828
+ # Add scope_id to the address
829
+ return f"{host}%{scope_id}", port
830
+ else:
831
+ return host, port
832
+ else:
833
+ return sockaddr
834
+
835
+
836
+ async def setup_unix_local_socket(
837
+ path: None | str | bytes | PathLike[Any],
838
+ mode: int | None,
839
+ socktype: int,
840
+ ) -> socket.socket:
841
+ """
842
+ Create a UNIX local socket object, deleting the socket at the given path if it
843
+ exists.
844
+
845
+ Not available on Windows.
846
+
847
+ :param path: path of the socket
848
+ :param mode: permissions to set on the socket
849
+ :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM
850
+
851
+ """
852
+ path_str: str | None
853
+ if path is not None:
854
+ path_str = os.fsdecode(path)
855
+
856
+ # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call
857
+ if not path_str.startswith("\0"):
858
+ # Copied from pathlib...
859
+ try:
860
+ stat_result = os.stat(path)
861
+ except OSError as e:
862
+ if e.errno not in (
863
+ errno.ENOENT,
864
+ errno.ENOTDIR,
865
+ errno.EBADF,
866
+ errno.ELOOP,
867
+ ):
868
+ raise
869
+ else:
870
+ if stat.S_ISSOCK(stat_result.st_mode):
871
+ os.unlink(path)
872
+ else:
873
+ path_str = None
874
+
875
+ raw_socket = socket.socket(socket.AF_UNIX, socktype)
876
+ raw_socket.setblocking(False)
877
+
878
+ if path_str is not None:
879
+ try:
880
+ await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True)
881
+ if mode is not None:
882
+ await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True)
883
+ except BaseException:
884
+ raw_socket.close()
885
+ raise
886
+
887
+ return raw_socket
888
+
889
+
890
+ @dataclass
891
+ class TCPConnectable(ByteStreamConnectable):
892
+ """
893
+ Connects to a TCP server at the given host and port.
894
+
895
+ :param host: host name or IP address of the server
896
+ :param port: TCP port number of the server
897
+ """
898
+
899
+ host: str | IPv4Address | IPv6Address
900
+ port: int
901
+
902
+ def __post_init__(self) -> None:
903
+ if self.port < 1 or self.port > 65535:
904
+ raise ValueError("TCP port number out of range")
905
+
906
+ @override
907
+ async def connect(self) -> SocketStream:
908
+ try:
909
+ return await connect_tcp(self.host, self.port)
910
+ except OSError as exc:
911
+ raise ConnectionFailed(
912
+ f"error connecting to {self.host}:{self.port}: {exc}"
913
+ ) from exc
914
+
915
+
916
+ @dataclass
917
+ class UNIXConnectable(ByteStreamConnectable):
918
+ """
919
+ Connects to a UNIX domain socket at the given path.
920
+
921
+ :param path: the file system path of the socket
922
+ """
923
+
924
+ path: str | bytes | PathLike[str] | PathLike[bytes]
925
+
926
+ @override
927
+ async def connect(self) -> UNIXSocketStream:
928
+ try:
929
+ return await connect_unix(self.path)
930
+ except OSError as exc:
931
+ raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc
932
+
933
+
934
+ def as_connectable(
935
+ remote: ByteStreamConnectable
936
+ | tuple[str | IPv4Address | IPv6Address, int]
937
+ | str
938
+ | bytes
939
+ | PathLike[str],
940
+ /,
941
+ *,
942
+ tls: bool = False,
943
+ ssl_context: ssl.SSLContext | None = None,
944
+ tls_hostname: str | None = None,
945
+ tls_standard_compatible: bool = True,
946
+ ) -> ByteStreamConnectable:
947
+ """
948
+ Return a byte stream connectable from the given object.
949
+
950
+ If a bytestream connectable is given, it is returned unchanged.
951
+ If a tuple of (host, port) is given, a TCP connectable is returned.
952
+ If a string or bytes path is given, a UNIX connectable is returned.
953
+
954
+ If ``tls=True``, the connectable will be wrapped in a
955
+ :class:`~.streams.tls.TLSConnectable`.
956
+
957
+ :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket
958
+ :param tls: if ``True``, wrap the plaintext connectable in a
959
+ :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings)
960
+ :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided,
961
+ a secure default will be created)
962
+ :param tls_hostname: if ``tls=True``, host name of the server to use for checking
963
+ the server certificate (defaults to the host portion of the address for TCP
964
+ connectables)
965
+ :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream
966
+ skip the closing handshake when closing the connection, so it won't raise an
967
+ exception if the server does the same
968
+
969
+ """
970
+ connectable: TCPConnectable | UNIXConnectable | TLSConnectable
971
+ if isinstance(remote, ByteStreamConnectable):
972
+ return remote
973
+ elif isinstance(remote, tuple) and len(remote) == 2:
974
+ connectable = TCPConnectable(*remote)
975
+ elif isinstance(remote, (str, bytes, PathLike)):
976
+ connectable = UNIXConnectable(remote)
977
+ else:
978
+ raise TypeError(f"cannot convert {remote!r} to a connectable")
979
+
980
+ if tls:
981
+ if not tls_hostname and isinstance(connectable, TCPConnectable):
982
+ tls_hostname = str(connectable.host)
983
+
984
+ connectable = TLSConnectable(
985
+ connectable,
986
+ ssl_context=ssl_context,
987
+ hostname=tls_hostname,
988
+ standard_compatible=tls_standard_compatible,
989
+ )
990
+
991
+ return connectable
backend/.venv/Lib/site-packages/anyio/_core/_streams.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import TypeVar
5
+ from warnings import warn
6
+
7
+ from ..streams.memory import (
8
+ MemoryObjectReceiveStream,
9
+ MemoryObjectSendStream,
10
+ _MemoryObjectStreamState,
11
+ )
12
+
13
+ T_Item = TypeVar("T_Item")
14
+
15
+
16
+ class create_memory_object_stream(
17
+ tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]],
18
+ ):
19
+ """
20
+ Create a memory object stream.
21
+
22
+ The stream's item type can be annotated like
23
+ :func:`create_memory_object_stream[T_Item]`.
24
+
25
+ :param max_buffer_size: number of items held in the buffer until ``send()`` starts
26
+ blocking
27
+ :param item_type: old way of marking the streams with the right generic type for
28
+ static typing (does nothing on AnyIO 4)
29
+
30
+ .. deprecated:: 4.0
31
+ Use ``create_memory_object_stream[YourItemType](...)`` instead.
32
+ :return: a tuple of (send stream, receive stream)
33
+
34
+ """
35
+
36
+ def __new__( # type: ignore[misc]
37
+ cls, max_buffer_size: float = 0, item_type: object = None
38
+ ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]:
39
+ if max_buffer_size != math.inf and not isinstance(max_buffer_size, int):
40
+ raise ValueError("max_buffer_size must be either an integer or math.inf")
41
+ if max_buffer_size < 0:
42
+ raise ValueError("max_buffer_size cannot be negative")
43
+ if item_type is not None:
44
+ warn(
45
+ "The item_type argument has been deprecated in AnyIO 4.0. "
46
+ "Use create_memory_object_stream[YourItemType](...) instead.",
47
+ DeprecationWarning,
48
+ stacklevel=2,
49
+ )
50
+
51
+ state = _MemoryObjectStreamState[T_Item](max_buffer_size)
52
+ return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state))
backend/.venv/Lib/site-packages/anyio/_core/_subprocesses.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from collections.abc import AsyncIterable, Iterable, Mapping, Sequence
5
+ from io import BytesIO
6
+ from os import PathLike
7
+ from subprocess import PIPE, CalledProcessError, CompletedProcess
8
+ from typing import IO, Any, Union, cast
9
+
10
+ from ..abc import Process
11
+ from ._eventloop import get_async_backend
12
+ from ._tasks import create_task_group
13
+
14
+ if sys.version_info >= (3, 10):
15
+ from typing import TypeAlias
16
+ else:
17
+ from typing_extensions import TypeAlias
18
+
19
+ StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"]
20
+
21
+
22
+ async def run_process(
23
+ command: StrOrBytesPath | Sequence[StrOrBytesPath],
24
+ *,
25
+ input: bytes | None = None,
26
+ stdin: int | IO[Any] | None = None,
27
+ stdout: int | IO[Any] | None = PIPE,
28
+ stderr: int | IO[Any] | None = PIPE,
29
+ check: bool = True,
30
+ cwd: StrOrBytesPath | None = None,
31
+ env: Mapping[str, str] | None = None,
32
+ startupinfo: Any = None,
33
+ creationflags: int = 0,
34
+ start_new_session: bool = False,
35
+ pass_fds: Sequence[int] = (),
36
+ user: str | int | None = None,
37
+ group: str | int | None = None,
38
+ extra_groups: Iterable[str | int] | None = None,
39
+ umask: int = -1,
40
+ ) -> CompletedProcess[bytes]:
41
+ """
42
+ Run an external command in a subprocess and wait until it completes.
43
+
44
+ .. seealso:: :func:`subprocess.run`
45
+
46
+ :param command: either a string to pass to the shell, or an iterable of strings
47
+ containing the executable name or path and its arguments
48
+ :param input: bytes passed to the standard input of the subprocess
49
+ :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
50
+ a file-like object, or `None`; ``input`` overrides this
51
+ :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
52
+ a file-like object, or `None`
53
+ :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
54
+ :data:`subprocess.STDOUT`, a file-like object, or `None`
55
+ :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the
56
+ process terminates with a return code other than 0
57
+ :param cwd: If not ``None``, change the working directory to this before running the
58
+ command
59
+ :param env: if not ``None``, this mapping replaces the inherited environment
60
+ variables from the parent process
61
+ :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
62
+ to specify process startup parameters (Windows only)
63
+ :param creationflags: flags that can be used to control the creation of the
64
+ subprocess (see :class:`subprocess.Popen` for the specifics)
65
+ :param start_new_session: if ``true`` the setsid() system call will be made in the
66
+ child process prior to the execution of the subprocess. (POSIX only)
67
+ :param pass_fds: sequence of file descriptors to keep open between the parent and
68
+ child processes. (POSIX only)
69
+ :param user: effective user to run the process as (Python >= 3.9, POSIX only)
70
+ :param group: effective group to run the process as (Python >= 3.9, POSIX only)
71
+ :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9,
72
+ POSIX only)
73
+ :param umask: if not negative, this umask is applied in the child process before
74
+ running the given command (Python >= 3.9, POSIX only)
75
+ :return: an object representing the completed process
76
+ :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process
77
+ exits with a nonzero return code
78
+
79
+ """
80
+
81
+ async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None:
82
+ buffer = BytesIO()
83
+ async for chunk in stream:
84
+ buffer.write(chunk)
85
+
86
+ stream_contents[index] = buffer.getvalue()
87
+
88
+ if stdin is not None and input is not None:
89
+ raise ValueError("only one of stdin and input is allowed")
90
+
91
+ async with await open_process(
92
+ command,
93
+ stdin=PIPE if input else stdin,
94
+ stdout=stdout,
95
+ stderr=stderr,
96
+ cwd=cwd,
97
+ env=env,
98
+ startupinfo=startupinfo,
99
+ creationflags=creationflags,
100
+ start_new_session=start_new_session,
101
+ pass_fds=pass_fds,
102
+ user=user,
103
+ group=group,
104
+ extra_groups=extra_groups,
105
+ umask=umask,
106
+ ) as process:
107
+ stream_contents: list[bytes | None] = [None, None]
108
+ async with create_task_group() as tg:
109
+ if process.stdout:
110
+ tg.start_soon(drain_stream, process.stdout, 0)
111
+
112
+ if process.stderr:
113
+ tg.start_soon(drain_stream, process.stderr, 1)
114
+
115
+ if process.stdin and input:
116
+ await process.stdin.send(input)
117
+ await process.stdin.aclose()
118
+
119
+ await process.wait()
120
+
121
+ output, errors = stream_contents
122
+ if check and process.returncode != 0:
123
+ raise CalledProcessError(cast(int, process.returncode), command, output, errors)
124
+
125
+ return CompletedProcess(command, cast(int, process.returncode), output, errors)
126
+
127
+
128
+ async def open_process(
129
+ command: StrOrBytesPath | Sequence[StrOrBytesPath],
130
+ *,
131
+ stdin: int | IO[Any] | None = PIPE,
132
+ stdout: int | IO[Any] | None = PIPE,
133
+ stderr: int | IO[Any] | None = PIPE,
134
+ cwd: StrOrBytesPath | None = None,
135
+ env: Mapping[str, str] | None = None,
136
+ startupinfo: Any = None,
137
+ creationflags: int = 0,
138
+ start_new_session: bool = False,
139
+ pass_fds: Sequence[int] = (),
140
+ user: str | int | None = None,
141
+ group: str | int | None = None,
142
+ extra_groups: Iterable[str | int] | None = None,
143
+ umask: int = -1,
144
+ ) -> Process:
145
+ """
146
+ Start an external command in a subprocess.
147
+
148
+ .. seealso:: :class:`subprocess.Popen`
149
+
150
+ :param command: either a string to pass to the shell, or an iterable of strings
151
+ containing the executable name or path and its arguments
152
+ :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a
153
+ file-like object, or ``None``
154
+ :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
155
+ a file-like object, or ``None``
156
+ :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
157
+ :data:`subprocess.STDOUT`, a file-like object, or ``None``
158
+ :param cwd: If not ``None``, the working directory is changed before executing
159
+ :param env: If env is not ``None``, it must be a mapping that defines the
160
+ environment variables for the new process
161
+ :param creationflags: flags that can be used to control the creation of the
162
+ subprocess (see :class:`subprocess.Popen` for the specifics)
163
+ :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
164
+ to specify process startup parameters (Windows only)
165
+ :param start_new_session: if ``true`` the setsid() system call will be made in the
166
+ child process prior to the execution of the subprocess. (POSIX only)
167
+ :param pass_fds: sequence of file descriptors to keep open between the parent and
168
+ child processes. (POSIX only)
169
+ :param user: effective user to run the process as (POSIX only)
170
+ :param group: effective group to run the process as (POSIX only)
171
+ :param extra_groups: supplementary groups to set in the subprocess (POSIX only)
172
+ :param umask: if not negative, this umask is applied in the child process before
173
+ running the given command (POSIX only)
174
+ :return: an asynchronous process object
175
+
176
+ """
177
+ kwargs: dict[str, Any] = {}
178
+ if user is not None:
179
+ kwargs["user"] = user
180
+
181
+ if group is not None:
182
+ kwargs["group"] = group
183
+
184
+ if extra_groups is not None:
185
+ kwargs["extra_groups"] = group
186
+
187
+ if umask >= 0:
188
+ kwargs["umask"] = umask
189
+
190
+ return await get_async_backend().open_process(
191
+ command,
192
+ stdin=stdin,
193
+ stdout=stdout,
194
+ stderr=stderr,
195
+ cwd=cwd,
196
+ env=env,
197
+ startupinfo=startupinfo,
198
+ creationflags=creationflags,
199
+ start_new_session=start_new_session,
200
+ pass_fds=pass_fds,
201
+ **kwargs,
202
+ )
backend/.venv/Lib/site-packages/anyio/_core/_synchronization.py ADDED
@@ -0,0 +1,753 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from collections import deque
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from types import TracebackType
8
+ from typing import TypeVar
9
+
10
+ from ..lowlevel import checkpoint_if_cancelled
11
+ from ._eventloop import NoCurrentAsyncBackend, get_async_backend
12
+ from ._exceptions import BusyResourceError
13
+ from ._tasks import CancelScope
14
+ from ._testing import TaskInfo, get_current_task
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class EventStatistics:
21
+ """
22
+ :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait`
23
+ """
24
+
25
+ tasks_waiting: int
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class CapacityLimiterStatistics:
30
+ """
31
+ :ivar int borrowed_tokens: number of tokens currently borrowed by tasks
32
+ :ivar float total_tokens: total number of available tokens
33
+ :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from
34
+ this limiter
35
+ :ivar int tasks_waiting: number of tasks waiting on
36
+ :meth:`~.CapacityLimiter.acquire` or
37
+ :meth:`~.CapacityLimiter.acquire_on_behalf_of`
38
+ """
39
+
40
+ borrowed_tokens: int
41
+ total_tokens: float
42
+ borrowers: tuple[object, ...]
43
+ tasks_waiting: int
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class LockStatistics:
48
+ """
49
+ :ivar bool locked: flag indicating if this lock is locked or not
50
+ :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the
51
+ lock is not held by any task)
52
+ :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire`
53
+ """
54
+
55
+ locked: bool
56
+ owner: TaskInfo | None
57
+ tasks_waiting: int
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class ConditionStatistics:
62
+ """
63
+ :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait`
64
+ :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying
65
+ :class:`~.Lock`
66
+ """
67
+
68
+ tasks_waiting: int
69
+ lock_statistics: LockStatistics
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class SemaphoreStatistics:
74
+ """
75
+ :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire`
76
+
77
+ """
78
+
79
+ tasks_waiting: int
80
+
81
+
82
+ class Event:
83
+ def __new__(cls) -> Event:
84
+ try:
85
+ return get_async_backend().create_event()
86
+ except NoCurrentAsyncBackend:
87
+ return EventAdapter()
88
+
89
+ def set(self) -> None:
90
+ """Set the flag, notifying all listeners."""
91
+ raise NotImplementedError
92
+
93
+ def is_set(self) -> bool:
94
+ """Return ``True`` if the flag is set, ``False`` if not."""
95
+ raise NotImplementedError
96
+
97
+ async def wait(self) -> None:
98
+ """
99
+ Wait until the flag has been set.
100
+
101
+ If the flag has already been set when this method is called, it returns
102
+ immediately.
103
+
104
+ """
105
+ raise NotImplementedError
106
+
107
+ def statistics(self) -> EventStatistics:
108
+ """Return statistics about the current state of this event."""
109
+ raise NotImplementedError
110
+
111
+
112
+ class EventAdapter(Event):
113
+ _internal_event: Event | None = None
114
+ _is_set: bool = False
115
+
116
+ def __new__(cls) -> EventAdapter:
117
+ return object.__new__(cls)
118
+
119
+ @property
120
+ def _event(self) -> Event:
121
+ if self._internal_event is None:
122
+ self._internal_event = get_async_backend().create_event()
123
+ if self._is_set:
124
+ self._internal_event.set()
125
+
126
+ return self._internal_event
127
+
128
+ def set(self) -> None:
129
+ if self._internal_event is None:
130
+ self._is_set = True
131
+ else:
132
+ self._event.set()
133
+
134
+ def is_set(self) -> bool:
135
+ if self._internal_event is None:
136
+ return self._is_set
137
+
138
+ return self._internal_event.is_set()
139
+
140
+ async def wait(self) -> None:
141
+ await self._event.wait()
142
+
143
+ def statistics(self) -> EventStatistics:
144
+ if self._internal_event is None:
145
+ return EventStatistics(tasks_waiting=0)
146
+
147
+ return self._internal_event.statistics()
148
+
149
+
150
+ class Lock:
151
+ def __new__(cls, *, fast_acquire: bool = False) -> Lock:
152
+ try:
153
+ return get_async_backend().create_lock(fast_acquire=fast_acquire)
154
+ except NoCurrentAsyncBackend:
155
+ return LockAdapter(fast_acquire=fast_acquire)
156
+
157
+ async def __aenter__(self) -> None:
158
+ await self.acquire()
159
+
160
+ async def __aexit__(
161
+ self,
162
+ exc_type: type[BaseException] | None,
163
+ exc_val: BaseException | None,
164
+ exc_tb: TracebackType | None,
165
+ ) -> None:
166
+ self.release()
167
+
168
+ async def acquire(self) -> None:
169
+ """Acquire the lock."""
170
+ raise NotImplementedError
171
+
172
+ def acquire_nowait(self) -> None:
173
+ """
174
+ Acquire the lock, without blocking.
175
+
176
+ :raises ~anyio.WouldBlock: if the operation would block
177
+
178
+ """
179
+ raise NotImplementedError
180
+
181
+ def release(self) -> None:
182
+ """Release the lock."""
183
+ raise NotImplementedError
184
+
185
+ def locked(self) -> bool:
186
+ """Return True if the lock is currently held."""
187
+ raise NotImplementedError
188
+
189
+ def statistics(self) -> LockStatistics:
190
+ """
191
+ Return statistics about the current state of this lock.
192
+
193
+ .. versionadded:: 3.0
194
+ """
195
+ raise NotImplementedError
196
+
197
+
198
+ class LockAdapter(Lock):
199
+ _internal_lock: Lock | None = None
200
+
201
+ def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter:
202
+ return object.__new__(cls)
203
+
204
+ def __init__(self, *, fast_acquire: bool = False):
205
+ self._fast_acquire = fast_acquire
206
+
207
+ @property
208
+ def _lock(self) -> Lock:
209
+ if self._internal_lock is None:
210
+ self._internal_lock = get_async_backend().create_lock(
211
+ fast_acquire=self._fast_acquire
212
+ )
213
+
214
+ return self._internal_lock
215
+
216
+ async def __aenter__(self) -> None:
217
+ await self._lock.acquire()
218
+
219
+ async def __aexit__(
220
+ self,
221
+ exc_type: type[BaseException] | None,
222
+ exc_val: BaseException | None,
223
+ exc_tb: TracebackType | None,
224
+ ) -> None:
225
+ if self._internal_lock is not None:
226
+ self._internal_lock.release()
227
+
228
+ async def acquire(self) -> None:
229
+ """Acquire the lock."""
230
+ await self._lock.acquire()
231
+
232
+ def acquire_nowait(self) -> None:
233
+ """
234
+ Acquire the lock, without blocking.
235
+
236
+ :raises ~anyio.WouldBlock: if the operation would block
237
+
238
+ """
239
+ self._lock.acquire_nowait()
240
+
241
+ def release(self) -> None:
242
+ """Release the lock."""
243
+ self._lock.release()
244
+
245
+ def locked(self) -> bool:
246
+ """Return True if the lock is currently held."""
247
+ return self._lock.locked()
248
+
249
+ def statistics(self) -> LockStatistics:
250
+ """
251
+ Return statistics about the current state of this lock.
252
+
253
+ .. versionadded:: 3.0
254
+
255
+ """
256
+ if self._internal_lock is None:
257
+ return LockStatistics(False, None, 0)
258
+
259
+ return self._internal_lock.statistics()
260
+
261
+
262
+ class Condition:
263
+ _owner_task: TaskInfo | None = None
264
+
265
+ def __init__(self, lock: Lock | None = None):
266
+ self._lock = lock or Lock()
267
+ self._waiters: deque[Event] = deque()
268
+
269
+ async def __aenter__(self) -> None:
270
+ await self.acquire()
271
+
272
+ async def __aexit__(
273
+ self,
274
+ exc_type: type[BaseException] | None,
275
+ exc_val: BaseException | None,
276
+ exc_tb: TracebackType | None,
277
+ ) -> None:
278
+ self.release()
279
+
280
+ def _check_acquired(self) -> None:
281
+ if self._owner_task != get_current_task():
282
+ raise RuntimeError("The current task is not holding the underlying lock")
283
+
284
+ async def acquire(self) -> None:
285
+ """Acquire the underlying lock."""
286
+ await self._lock.acquire()
287
+ self._owner_task = get_current_task()
288
+
289
+ def acquire_nowait(self) -> None:
290
+ """
291
+ Acquire the underlying lock, without blocking.
292
+
293
+ :raises ~anyio.WouldBlock: if the operation would block
294
+
295
+ """
296
+ self._lock.acquire_nowait()
297
+ self._owner_task = get_current_task()
298
+
299
+ def release(self) -> None:
300
+ """Release the underlying lock."""
301
+ self._lock.release()
302
+
303
+ def locked(self) -> bool:
304
+ """Return True if the lock is set."""
305
+ return self._lock.locked()
306
+
307
+ def notify(self, n: int = 1) -> None:
308
+ """Notify exactly n listeners."""
309
+ self._check_acquired()
310
+ for _ in range(n):
311
+ try:
312
+ event = self._waiters.popleft()
313
+ except IndexError:
314
+ break
315
+
316
+ event.set()
317
+
318
+ def notify_all(self) -> None:
319
+ """Notify all the listeners."""
320
+ self._check_acquired()
321
+ for event in self._waiters:
322
+ event.set()
323
+
324
+ self._waiters.clear()
325
+
326
+ async def wait(self) -> None:
327
+ """Wait for a notification."""
328
+ await checkpoint_if_cancelled()
329
+ self._check_acquired()
330
+ event = Event()
331
+ self._waiters.append(event)
332
+ self.release()
333
+ try:
334
+ await event.wait()
335
+ except BaseException:
336
+ if not event.is_set():
337
+ self._waiters.remove(event)
338
+
339
+ raise
340
+ finally:
341
+ with CancelScope(shield=True):
342
+ await self.acquire()
343
+
344
+ async def wait_for(self, predicate: Callable[[], T]) -> T:
345
+ """
346
+ Wait until a predicate becomes true.
347
+
348
+ :param predicate: a callable that returns a truthy value when the condition is
349
+ met
350
+ :return: the result of the predicate
351
+
352
+ .. versionadded:: 4.11.0
353
+
354
+ """
355
+ while not (result := predicate()):
356
+ await self.wait()
357
+
358
+ return result
359
+
360
+ def statistics(self) -> ConditionStatistics:
361
+ """
362
+ Return statistics about the current state of this condition.
363
+
364
+ .. versionadded:: 3.0
365
+ """
366
+ return ConditionStatistics(len(self._waiters), self._lock.statistics())
367
+
368
+
369
+ class Semaphore:
370
+ def __new__(
371
+ cls,
372
+ initial_value: int,
373
+ *,
374
+ max_value: int | None = None,
375
+ fast_acquire: bool = False,
376
+ ) -> Semaphore:
377
+ try:
378
+ return get_async_backend().create_semaphore(
379
+ initial_value, max_value=max_value, fast_acquire=fast_acquire
380
+ )
381
+ except NoCurrentAsyncBackend:
382
+ return SemaphoreAdapter(initial_value, max_value=max_value)
383
+
384
+ def __init__(
385
+ self,
386
+ initial_value: int,
387
+ *,
388
+ max_value: int | None = None,
389
+ fast_acquire: bool = False,
390
+ ):
391
+ if not isinstance(initial_value, int):
392
+ raise TypeError("initial_value must be an integer")
393
+ if initial_value < 0:
394
+ raise ValueError("initial_value must be >= 0")
395
+ if max_value is not None:
396
+ if not isinstance(max_value, int):
397
+ raise TypeError("max_value must be an integer or None")
398
+ if max_value < initial_value:
399
+ raise ValueError(
400
+ "max_value must be equal to or higher than initial_value"
401
+ )
402
+
403
+ self._fast_acquire = fast_acquire
404
+
405
+ async def __aenter__(self) -> Semaphore:
406
+ await self.acquire()
407
+ return self
408
+
409
+ async def __aexit__(
410
+ self,
411
+ exc_type: type[BaseException] | None,
412
+ exc_val: BaseException | None,
413
+ exc_tb: TracebackType | None,
414
+ ) -> None:
415
+ self.release()
416
+
417
+ async def acquire(self) -> None:
418
+ """Decrement the semaphore value, blocking if necessary."""
419
+ raise NotImplementedError
420
+
421
+ def acquire_nowait(self) -> None:
422
+ """
423
+ Acquire the underlying lock, without blocking.
424
+
425
+ :raises ~anyio.WouldBlock: if the operation would block
426
+
427
+ """
428
+ raise NotImplementedError
429
+
430
+ def release(self) -> None:
431
+ """Increment the semaphore value."""
432
+ raise NotImplementedError
433
+
434
+ @property
435
+ def value(self) -> int:
436
+ """The current value of the semaphore."""
437
+ raise NotImplementedError
438
+
439
+ @property
440
+ def max_value(self) -> int | None:
441
+ """The maximum value of the semaphore."""
442
+ raise NotImplementedError
443
+
444
+ def statistics(self) -> SemaphoreStatistics:
445
+ """
446
+ Return statistics about the current state of this semaphore.
447
+
448
+ .. versionadded:: 3.0
449
+ """
450
+ raise NotImplementedError
451
+
452
+
453
+ class SemaphoreAdapter(Semaphore):
454
+ _internal_semaphore: Semaphore | None = None
455
+
456
+ def __new__(
457
+ cls,
458
+ initial_value: int,
459
+ *,
460
+ max_value: int | None = None,
461
+ fast_acquire: bool = False,
462
+ ) -> SemaphoreAdapter:
463
+ return object.__new__(cls)
464
+
465
+ def __init__(
466
+ self,
467
+ initial_value: int,
468
+ *,
469
+ max_value: int | None = None,
470
+ fast_acquire: bool = False,
471
+ ) -> None:
472
+ super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
473
+ self._initial_value = initial_value
474
+ self._max_value = max_value
475
+
476
+ @property
477
+ def _semaphore(self) -> Semaphore:
478
+ if self._internal_semaphore is None:
479
+ self._internal_semaphore = get_async_backend().create_semaphore(
480
+ self._initial_value, max_value=self._max_value
481
+ )
482
+
483
+ return self._internal_semaphore
484
+
485
+ async def acquire(self) -> None:
486
+ await self._semaphore.acquire()
487
+
488
+ def acquire_nowait(self) -> None:
489
+ self._semaphore.acquire_nowait()
490
+
491
+ def release(self) -> None:
492
+ self._semaphore.release()
493
+
494
+ @property
495
+ def value(self) -> int:
496
+ if self._internal_semaphore is None:
497
+ return self._initial_value
498
+
499
+ return self._semaphore.value
500
+
501
+ @property
502
+ def max_value(self) -> int | None:
503
+ return self._max_value
504
+
505
+ def statistics(self) -> SemaphoreStatistics:
506
+ if self._internal_semaphore is None:
507
+ return SemaphoreStatistics(tasks_waiting=0)
508
+
509
+ return self._semaphore.statistics()
510
+
511
+
512
+ class CapacityLimiter:
513
+ def __new__(cls, total_tokens: float) -> CapacityLimiter:
514
+ try:
515
+ return get_async_backend().create_capacity_limiter(total_tokens)
516
+ except NoCurrentAsyncBackend:
517
+ return CapacityLimiterAdapter(total_tokens)
518
+
519
+ async def __aenter__(self) -> None:
520
+ raise NotImplementedError
521
+
522
+ async def __aexit__(
523
+ self,
524
+ exc_type: type[BaseException] | None,
525
+ exc_val: BaseException | None,
526
+ exc_tb: TracebackType | None,
527
+ ) -> None:
528
+ raise NotImplementedError
529
+
530
+ @property
531
+ def total_tokens(self) -> float:
532
+ """
533
+ The total number of tokens available for borrowing.
534
+
535
+ This is a read-write property. If the total number of tokens is increased, the
536
+ proportionate number of tasks waiting on this limiter will be granted their
537
+ tokens.
538
+
539
+ .. versionchanged:: 3.0
540
+ The property is now writable.
541
+ .. versionchanged:: 4.12
542
+ The value can now be set to 0.
543
+
544
+ """
545
+ raise NotImplementedError
546
+
547
+ @total_tokens.setter
548
+ def total_tokens(self, value: float) -> None:
549
+ raise NotImplementedError
550
+
551
+ @property
552
+ def borrowed_tokens(self) -> int:
553
+ """The number of tokens that have currently been borrowed."""
554
+ raise NotImplementedError
555
+
556
+ @property
557
+ def available_tokens(self) -> float:
558
+ """The number of tokens currently available to be borrowed"""
559
+ raise NotImplementedError
560
+
561
+ def acquire_nowait(self) -> None:
562
+ """
563
+ Acquire a token for the current task without waiting for one to become
564
+ available.
565
+
566
+ :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
567
+
568
+ """
569
+ raise NotImplementedError
570
+
571
+ def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
572
+ """
573
+ Acquire a token without waiting for one to become available.
574
+
575
+ :param borrower: the entity borrowing a token
576
+ :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
577
+
578
+ """
579
+ raise NotImplementedError
580
+
581
+ async def acquire(self) -> None:
582
+ """
583
+ Acquire a token for the current task, waiting if necessary for one to become
584
+ available.
585
+
586
+ """
587
+ raise NotImplementedError
588
+
589
+ async def acquire_on_behalf_of(self, borrower: object) -> None:
590
+ """
591
+ Acquire a token, waiting if necessary for one to become available.
592
+
593
+ :param borrower: the entity borrowing a token
594
+
595
+ """
596
+ raise NotImplementedError
597
+
598
+ def release(self) -> None:
599
+ """
600
+ Release the token held by the current task.
601
+
602
+ :raises RuntimeError: if the current task has not borrowed a token from this
603
+ limiter.
604
+
605
+ """
606
+ raise NotImplementedError
607
+
608
+ def release_on_behalf_of(self, borrower: object) -> None:
609
+ """
610
+ Release the token held by the given borrower.
611
+
612
+ :raises RuntimeError: if the borrower has not borrowed a token from this
613
+ limiter.
614
+
615
+ """
616
+ raise NotImplementedError
617
+
618
+ def statistics(self) -> CapacityLimiterStatistics:
619
+ """
620
+ Return statistics about the current state of this limiter.
621
+
622
+ .. versionadded:: 3.0
623
+
624
+ """
625
+ raise NotImplementedError
626
+
627
+
628
+ class CapacityLimiterAdapter(CapacityLimiter):
629
+ _internal_limiter: CapacityLimiter | None = None
630
+
631
+ def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter:
632
+ return object.__new__(cls)
633
+
634
+ def __init__(self, total_tokens: float) -> None:
635
+ self.total_tokens = total_tokens
636
+
637
+ @property
638
+ def _limiter(self) -> CapacityLimiter:
639
+ if self._internal_limiter is None:
640
+ self._internal_limiter = get_async_backend().create_capacity_limiter(
641
+ self._total_tokens
642
+ )
643
+
644
+ return self._internal_limiter
645
+
646
+ async def __aenter__(self) -> None:
647
+ await self._limiter.__aenter__()
648
+
649
+ async def __aexit__(
650
+ self,
651
+ exc_type: type[BaseException] | None,
652
+ exc_val: BaseException | None,
653
+ exc_tb: TracebackType | None,
654
+ ) -> None:
655
+ return await self._limiter.__aexit__(exc_type, exc_val, exc_tb)
656
+
657
+ @property
658
+ def total_tokens(self) -> float:
659
+ if self._internal_limiter is None:
660
+ return self._total_tokens
661
+
662
+ return self._internal_limiter.total_tokens
663
+
664
+ @total_tokens.setter
665
+ def total_tokens(self, value: float) -> None:
666
+ if not isinstance(value, int) and value is not math.inf:
667
+ raise TypeError("total_tokens must be an int or math.inf")
668
+ elif value < 1:
669
+ raise ValueError("total_tokens must be >= 1")
670
+
671
+ if self._internal_limiter is None:
672
+ self._total_tokens = value
673
+ return
674
+
675
+ self._limiter.total_tokens = value
676
+
677
+ @property
678
+ def borrowed_tokens(self) -> int:
679
+ if self._internal_limiter is None:
680
+ return 0
681
+
682
+ return self._internal_limiter.borrowed_tokens
683
+
684
+ @property
685
+ def available_tokens(self) -> float:
686
+ if self._internal_limiter is None:
687
+ return self._total_tokens
688
+
689
+ return self._internal_limiter.available_tokens
690
+
691
+ def acquire_nowait(self) -> None:
692
+ self._limiter.acquire_nowait()
693
+
694
+ def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
695
+ self._limiter.acquire_on_behalf_of_nowait(borrower)
696
+
697
+ async def acquire(self) -> None:
698
+ await self._limiter.acquire()
699
+
700
+ async def acquire_on_behalf_of(self, borrower: object) -> None:
701
+ await self._limiter.acquire_on_behalf_of(borrower)
702
+
703
+ def release(self) -> None:
704
+ self._limiter.release()
705
+
706
+ def release_on_behalf_of(self, borrower: object) -> None:
707
+ self._limiter.release_on_behalf_of(borrower)
708
+
709
+ def statistics(self) -> CapacityLimiterStatistics:
710
+ if self._internal_limiter is None:
711
+ return CapacityLimiterStatistics(
712
+ borrowed_tokens=0,
713
+ total_tokens=self.total_tokens,
714
+ borrowers=(),
715
+ tasks_waiting=0,
716
+ )
717
+
718
+ return self._internal_limiter.statistics()
719
+
720
+
721
+ class ResourceGuard:
722
+ """
723
+ A context manager for ensuring that a resource is only used by a single task at a
724
+ time.
725
+
726
+ Entering this context manager while the previous has not exited it yet will trigger
727
+ :exc:`BusyResourceError`.
728
+
729
+ :param action: the action to guard against (visible in the :exc:`BusyResourceError`
730
+ when triggered, e.g. "Another task is already {action} this resource")
731
+
732
+ .. versionadded:: 4.1
733
+ """
734
+
735
+ __slots__ = "action", "_guarded"
736
+
737
+ def __init__(self, action: str = "using"):
738
+ self.action: str = action
739
+ self._guarded = False
740
+
741
+ def __enter__(self) -> None:
742
+ if self._guarded:
743
+ raise BusyResourceError(self.action)
744
+
745
+ self._guarded = True
746
+
747
+ def __exit__(
748
+ self,
749
+ exc_type: type[BaseException] | None,
750
+ exc_val: BaseException | None,
751
+ exc_tb: TracebackType | None,
752
+ ) -> None:
753
+ self._guarded = False