diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index ab1f416..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Ignored default folder with query files
-/queries/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
deleted file mode 100644
index 1f2ea11..0000000
--- a/.idea/copilot.data.migration.ask2agent.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
deleted file mode 100644
index 518f338..0000000
--- a/.idea/dataSources.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- postgresql
- true
- org.postgresql.Driver
- jdbc:postgresql://localhost:5432/postgres
-
-
-
-
-
- $ProjectFileDir$
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index fb0b0ea..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 56da3b3..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- {}
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 5d7c327..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/openclaw-agency.iml b/.idea/openclaw-agency.iml
deleted file mode 100644
index f571432..0000000
--- a/.idea/openclaw-agency.iml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/.venv/bin/Activate.ps1 b/backend/.venv/bin/Activate.ps1
deleted file mode 100644
index eeea358..0000000
--- a/backend/.venv/bin/Activate.ps1
+++ /dev/null
@@ -1,247 +0,0 @@
-<#
-.Synopsis
-Activate a Python virtual environment for the current PowerShell session.
-
-.Description
-Pushes the python executable for a virtual environment to the front of the
-$Env:PATH environment variable and sets the prompt to signify that you are
-in a Python virtual environment. Makes use of the command line switches as
-well as the `pyvenv.cfg` file values present in the virtual environment.
-
-.Parameter VenvDir
-Path to the directory that contains the virtual environment to activate. The
-default value for this is the parent of the directory that the Activate.ps1
-script is located within.
-
-.Parameter Prompt
-The prompt prefix to display when this virtual environment is activated. By
-default, this prompt is the name of the virtual environment folder (VenvDir)
-surrounded by parentheses and followed by a single space (ie. '(.venv) ').
-
-.Example
-Activate.ps1
-Activates the Python virtual environment that contains the Activate.ps1 script.
-
-.Example
-Activate.ps1 -Verbose
-Activates the Python virtual environment that contains the Activate.ps1 script,
-and shows extra information about the activation as it executes.
-
-.Example
-Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
-Activates the Python virtual environment located in the specified location.
-
-.Example
-Activate.ps1 -Prompt "MyPython"
-Activates the Python virtual environment that contains the Activate.ps1 script,
-and prefixes the current prompt with the specified string (surrounded in
-parentheses) while the virtual environment is active.
-
-.Notes
-On Windows, it may be required to enable this Activate.ps1 script by setting the
-execution policy for the user. You can do this by issuing the following PowerShell
-command:
-
-PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
-
-For more information on Execution Policies:
-https://go.microsoft.com/fwlink/?LinkID=135170
-
-#>
-Param(
- [Parameter(Mandatory = $false)]
- [String]
- $VenvDir,
- [Parameter(Mandatory = $false)]
- [String]
- $Prompt
-)
-
-<# Function declarations --------------------------------------------------- #>
-
-<#
-.Synopsis
-Remove all shell session elements added by the Activate script, including the
-addition of the virtual environment's Python executable from the beginning of
-the PATH variable.
-
-.Parameter NonDestructive
-If present, do not remove this function from the global namespace for the
-session.
-
-#>
-function global:deactivate ([switch]$NonDestructive) {
- # Revert to original values
-
- # The prior prompt:
- if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
- Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
- Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
- }
-
- # The prior PYTHONHOME:
- if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
- Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
- Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
- }
-
- # The prior PATH:
- if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
- Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
- Remove-Item -Path Env:_OLD_VIRTUAL_PATH
- }
-
- # Just remove the VIRTUAL_ENV altogether:
- if (Test-Path -Path Env:VIRTUAL_ENV) {
- Remove-Item -Path env:VIRTUAL_ENV
- }
-
- # Just remove VIRTUAL_ENV_PROMPT altogether.
- if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
- Remove-Item -Path env:VIRTUAL_ENV_PROMPT
- }
-
- # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
- if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
- Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
- }
-
- # Leave deactivate function in the global namespace if requested:
- if (-not $NonDestructive) {
- Remove-Item -Path function:deactivate
- }
-}
-
-<#
-.Description
-Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
-given folder, and returns them in a map.
-
-For each line in the pyvenv.cfg file, if that line can be parsed into exactly
-two strings separated by `=` (with any amount of whitespace surrounding the =)
-then it is considered a `key = value` line. The left hand string is the key,
-the right hand is the value.
-
-If the value starts with a `'` or a `"` then the first and last character is
-stripped from the value before being captured.
-
-.Parameter ConfigDir
-Path to the directory that contains the `pyvenv.cfg` file.
-#>
-function Get-PyVenvConfig(
- [String]
- $ConfigDir
-) {
- Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
-
- # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
- $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
-
- # An empty map will be returned if no config file is found.
- $pyvenvConfig = @{ }
-
- if ($pyvenvConfigPath) {
-
- Write-Verbose "File exists, parse `key = value` lines"
- $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
-
- $pyvenvConfigContent | ForEach-Object {
- $keyval = $PSItem -split "\s*=\s*", 2
- if ($keyval[0] -and $keyval[1]) {
- $val = $keyval[1]
-
- # Remove extraneous quotations around a string value.
- if ("'""".Contains($val.Substring(0, 1))) {
- $val = $val.Substring(1, $val.Length - 2)
- }
-
- $pyvenvConfig[$keyval[0]] = $val
- Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
- }
- }
- }
- return $pyvenvConfig
-}
-
-
-<# Begin Activate script --------------------------------------------------- #>
-
-# Determine the containing directory of this script
-$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
-$VenvExecDir = Get-Item -Path $VenvExecPath
-
-Write-Verbose "Activation script is located in path: '$VenvExecPath'"
-Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
-Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
-
-# Set values required in priority: CmdLine, ConfigFile, Default
-# First, get the location of the virtual environment, it might not be
-# VenvExecDir if specified on the command line.
-if ($VenvDir) {
- Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
-}
-else {
- Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
- $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
- Write-Verbose "VenvDir=$VenvDir"
-}
-
-# Next, read the `pyvenv.cfg` file to determine any required value such
-# as `prompt`.
-$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
-
-# Next, set the prompt from the command line, or the config file, or
-# just use the name of the virtual environment folder.
-if ($Prompt) {
- Write-Verbose "Prompt specified as argument, using '$Prompt'"
-}
-else {
- Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
- if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
- Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
- $Prompt = $pyvenvCfg['prompt'];
- }
- else {
- Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
- Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
- $Prompt = Split-Path -Path $venvDir -Leaf
- }
-}
-
-Write-Verbose "Prompt = '$Prompt'"
-Write-Verbose "VenvDir='$VenvDir'"
-
-# Deactivate any currently active virtual environment, but leave the
-# deactivate function in place.
-deactivate -nondestructive
-
-# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
-# that there is an activated venv.
-$env:VIRTUAL_ENV = $VenvDir
-
-if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
-
- Write-Verbose "Setting prompt to '$Prompt'"
-
- # Set the prompt to include the env name
- # Make sure _OLD_VIRTUAL_PROMPT is global
- function global:_OLD_VIRTUAL_PROMPT { "" }
- Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
- New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
-
- function global:prompt {
- Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
- _OLD_VIRTUAL_PROMPT
- }
- $env:VIRTUAL_ENV_PROMPT = $Prompt
-}
-
-# Clear PYTHONHOME
-if (Test-Path -Path Env:PYTHONHOME) {
- Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
- Remove-Item -Path Env:PYTHONHOME
-}
-
-# Add the venv to the PATH
-Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
-$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
diff --git a/backend/.venv/bin/activate b/backend/.venv/bin/activate
deleted file mode 100644
index a7a3138..0000000
--- a/backend/.venv/bin/activate
+++ /dev/null
@@ -1,70 +0,0 @@
-# This file must be used with "source bin/activate" *from bash*
-# You cannot run it directly
-
-deactivate () {
- # reset old environment variables
- if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
- PATH="${_OLD_VIRTUAL_PATH:-}"
- export PATH
- unset _OLD_VIRTUAL_PATH
- fi
- if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
- PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
- export PYTHONHOME
- unset _OLD_VIRTUAL_PYTHONHOME
- fi
-
- # Call hash to forget past commands. Without forgetting
- # past commands the $PATH changes we made may not be respected
- hash -r 2> /dev/null
-
- if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
- PS1="${_OLD_VIRTUAL_PS1:-}"
- export PS1
- unset _OLD_VIRTUAL_PS1
- fi
-
- unset VIRTUAL_ENV
- unset VIRTUAL_ENV_PROMPT
- if [ ! "${1:-}" = "nondestructive" ] ; then
- # Self destruct!
- unset -f deactivate
- fi
-}
-
-# unset irrelevant variables
-deactivate nondestructive
-
-# on Windows, a path can contain colons and backslashes and has to be converted:
-if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
- # transform D:\path\to\venv to /d/path/to/venv on MSYS
- # and to /cygdrive/d/path/to/venv on Cygwin
- export VIRTUAL_ENV=$(cygpath /home/asaharan/PycharmProjects/openclaw-agency/backend/.venv)
-else
- # use the path as-is
- export VIRTUAL_ENV=/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv
-fi
-
-_OLD_VIRTUAL_PATH="$PATH"
-PATH="$VIRTUAL_ENV/"bin":$PATH"
-export PATH
-
-# unset PYTHONHOME if set
-# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
-# could use `if (set -u; : $PYTHONHOME) ;` in bash
-if [ -n "${PYTHONHOME:-}" ] ; then
- _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
- unset PYTHONHOME
-fi
-
-if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
- _OLD_VIRTUAL_PS1="${PS1:-}"
- PS1='(.venv) '"${PS1:-}"
- export PS1
- VIRTUAL_ENV_PROMPT='(.venv) '
- export VIRTUAL_ENV_PROMPT
-fi
-
-# Call hash to forget past commands. Without forgetting
-# past commands the $PATH changes we made may not be respected
-hash -r 2> /dev/null
diff --git a/backend/.venv/bin/activate.csh b/backend/.venv/bin/activate.csh
deleted file mode 100644
index e939d72..0000000
--- a/backend/.venv/bin/activate.csh
+++ /dev/null
@@ -1,27 +0,0 @@
-# This file must be used with "source bin/activate.csh" *from csh*.
-# You cannot run it directly.
-
-# Created by Davide Di Blasi .
-# Ported to Python 3.3 venv by Andrew Svetlov
-
-alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
-
-# Unset irrelevant variables.
-deactivate nondestructive
-
-setenv VIRTUAL_ENV /home/asaharan/PycharmProjects/openclaw-agency/backend/.venv
-
-set _OLD_VIRTUAL_PATH="$PATH"
-setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
-
-
-set _OLD_VIRTUAL_PROMPT="$prompt"
-
-if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
- set prompt = '(.venv) '"$prompt"
- setenv VIRTUAL_ENV_PROMPT '(.venv) '
-endif
-
-alias pydoc python -m pydoc
-
-rehash
diff --git a/backend/.venv/bin/activate.fish b/backend/.venv/bin/activate.fish
deleted file mode 100644
index 8fcf346..0000000
--- a/backend/.venv/bin/activate.fish
+++ /dev/null
@@ -1,69 +0,0 @@
-# This file must be used with "source /bin/activate.fish" *from fish*
-# (https://fishshell.com/). You cannot run it directly.
-
-function deactivate -d "Exit virtual environment and return to normal shell environment"
- # reset old environment variables
- if test -n "$_OLD_VIRTUAL_PATH"
- set -gx PATH $_OLD_VIRTUAL_PATH
- set -e _OLD_VIRTUAL_PATH
- end
- if test -n "$_OLD_VIRTUAL_PYTHONHOME"
- set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
- set -e _OLD_VIRTUAL_PYTHONHOME
- end
-
- if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
- set -e _OLD_FISH_PROMPT_OVERRIDE
- # prevents error when using nested fish instances (Issue #93858)
- if functions -q _old_fish_prompt
- functions -e fish_prompt
- functions -c _old_fish_prompt fish_prompt
- functions -e _old_fish_prompt
- end
- end
-
- set -e VIRTUAL_ENV
- set -e VIRTUAL_ENV_PROMPT
- if test "$argv[1]" != "nondestructive"
- # Self-destruct!
- functions -e deactivate
- end
-end
-
-# Unset irrelevant variables.
-deactivate nondestructive
-
-set -gx VIRTUAL_ENV /home/asaharan/PycharmProjects/openclaw-agency/backend/.venv
-
-set -gx _OLD_VIRTUAL_PATH $PATH
-set -gx PATH "$VIRTUAL_ENV/"bin $PATH
-
-# Unset PYTHONHOME if set.
-if set -q PYTHONHOME
- set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
- set -e PYTHONHOME
-end
-
-if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
- # fish uses a function instead of an env var to generate the prompt.
-
- # Save the current fish_prompt function as the function _old_fish_prompt.
- functions -c fish_prompt _old_fish_prompt
-
- # With the original prompt function renamed, we can override with our own.
- function fish_prompt
- # Save the return status of the last command.
- set -l old_status $status
-
- # Output the venv prompt; color taken from the blue of the Python logo.
- printf "%s%s%s" (set_color 4B8BBE) '(.venv) ' (set_color normal)
-
- # Restore the return status of the previous command.
- echo "exit $old_status" | .
- # Output the original/"old" prompt.
- _old_fish_prompt
- end
-
- set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
- set -gx VIRTUAL_ENV_PROMPT '(.venv) '
-end
diff --git a/backend/.venv/bin/alembic b/backend/.venv/bin/alembic
deleted file mode 100755
index 89de2ff..0000000
--- a/backend/.venv/bin/alembic
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from alembic.config import main
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(main())
diff --git a/backend/.venv/bin/dotenv b/backend/.venv/bin/dotenv
deleted file mode 100755
index a7bf4f4..0000000
--- a/backend/.venv/bin/dotenv
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from dotenv.__main__ import cli
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(cli())
diff --git a/backend/.venv/bin/fastapi b/backend/.venv/bin/fastapi
deleted file mode 100755
index 4c6a873..0000000
--- a/backend/.venv/bin/fastapi
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from fastapi.cli import main
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(main())
diff --git a/backend/.venv/bin/mako-render b/backend/.venv/bin/mako-render
deleted file mode 100755
index 0b7d749..0000000
--- a/backend/.venv/bin/mako-render
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from mako.cmd import cmdline
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(cmdline())
diff --git a/backend/.venv/bin/pip b/backend/.venv/bin/pip
deleted file mode 100755
index 51a3e33..0000000
--- a/backend/.venv/bin/pip
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-# -*- coding: utf-8 -*-
-import re
-import sys
-from pip._internal.cli.main import main
-if __name__ == '__main__':
- sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
diff --git a/backend/.venv/bin/pip3 b/backend/.venv/bin/pip3
deleted file mode 100755
index 51a3e33..0000000
--- a/backend/.venv/bin/pip3
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-# -*- coding: utf-8 -*-
-import re
-import sys
-from pip._internal.cli.main import main
-if __name__ == '__main__':
- sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
diff --git a/backend/.venv/bin/pip3.12 b/backend/.venv/bin/pip3.12
deleted file mode 100755
index 51a3e33..0000000
--- a/backend/.venv/bin/pip3.12
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-# -*- coding: utf-8 -*-
-import re
-import sys
-from pip._internal.cli.main import main
-if __name__ == '__main__':
- sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
diff --git a/backend/.venv/bin/python b/backend/.venv/bin/python
deleted file mode 120000
index b8a0adb..0000000
--- a/backend/.venv/bin/python
+++ /dev/null
@@ -1 +0,0 @@
-python3
\ No newline at end of file
diff --git a/backend/.venv/bin/python3 b/backend/.venv/bin/python3
deleted file mode 120000
index ae65fda..0000000
--- a/backend/.venv/bin/python3
+++ /dev/null
@@ -1 +0,0 @@
-/usr/bin/python3
\ No newline at end of file
diff --git a/backend/.venv/bin/python3.12 b/backend/.venv/bin/python3.12
deleted file mode 120000
index b8a0adb..0000000
--- a/backend/.venv/bin/python3.12
+++ /dev/null
@@ -1 +0,0 @@
-python3
\ No newline at end of file
diff --git a/backend/.venv/bin/uvicorn b/backend/.venv/bin/uvicorn
deleted file mode 100755
index 5bb2bdd..0000000
--- a/backend/.venv/bin/uvicorn
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from uvicorn.main import main
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(main())
diff --git a/backend/.venv/bin/watchfiles b/backend/.venv/bin/watchfiles
deleted file mode 100755
index 6d3356c..0000000
--- a/backend/.venv/bin/watchfiles
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from watchfiles.cli import cli
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(cli())
diff --git a/backend/.venv/bin/websockets b/backend/.venv/bin/websockets
deleted file mode 100755
index 28efae3..0000000
--- a/backend/.venv/bin/websockets
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/home/asaharan/PycharmProjects/openclaw-agency/backend/.venv/bin/python
-import sys
-from websockets.cli import main
-if __name__ == '__main__':
- sys.argv[0] = sys.argv[0].removesuffix('.exe')
- sys.exit(main())
diff --git a/backend/.venv/include/site/python3.12/greenlet/greenlet.h b/backend/.venv/include/site/python3.12/greenlet/greenlet.h
deleted file mode 100644
index d02a16e..0000000
--- a/backend/.venv/include/site/python3.12/greenlet/greenlet.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-
-/* Greenlet object interface */
-
-#ifndef Py_GREENLETOBJECT_H
-#define Py_GREENLETOBJECT_H
-
-
-#include
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* This is deprecated and undocumented. It does not change. */
-#define GREENLET_VERSION "1.0.0"
-
-#ifndef GREENLET_MODULE
-#define implementation_ptr_t void*
-#endif
-
-typedef struct _greenlet {
- PyObject_HEAD
- PyObject* weakreflist;
- PyObject* dict;
- implementation_ptr_t pimpl;
-} PyGreenlet;
-
-#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
-
-
-/* C API functions */
-
-/* Total number of symbols that are exported */
-#define PyGreenlet_API_pointers 12
-
-#define PyGreenlet_Type_NUM 0
-#define PyExc_GreenletError_NUM 1
-#define PyExc_GreenletExit_NUM 2
-
-#define PyGreenlet_New_NUM 3
-#define PyGreenlet_GetCurrent_NUM 4
-#define PyGreenlet_Throw_NUM 5
-#define PyGreenlet_Switch_NUM 6
-#define PyGreenlet_SetParent_NUM 7
-
-#define PyGreenlet_MAIN_NUM 8
-#define PyGreenlet_STARTED_NUM 9
-#define PyGreenlet_ACTIVE_NUM 10
-#define PyGreenlet_GET_PARENT_NUM 11
-
-#ifndef GREENLET_MODULE
-/* This section is used by modules that uses the greenlet C API */
-static void** _PyGreenlet_API = NULL;
-
-# define PyGreenlet_Type \
- (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
-
-# define PyExc_GreenletError \
- ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
-
-# define PyExc_GreenletExit \
- ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
-
-/*
- * PyGreenlet_New(PyObject *args)
- *
- * greenlet.greenlet(run, parent=None)
- */
-# define PyGreenlet_New \
- (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
- _PyGreenlet_API[PyGreenlet_New_NUM])
-
-/*
- * PyGreenlet_GetCurrent(void)
- *
- * greenlet.getcurrent()
- */
-# define PyGreenlet_GetCurrent \
- (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
-
-/*
- * PyGreenlet_Throw(
- * PyGreenlet *greenlet,
- * PyObject *typ,
- * PyObject *val,
- * PyObject *tb)
- *
- * g.throw(...)
- */
-# define PyGreenlet_Throw \
- (*(PyObject * (*)(PyGreenlet * self, \
- PyObject * typ, \
- PyObject * val, \
- PyObject * tb)) \
- _PyGreenlet_API[PyGreenlet_Throw_NUM])
-
-/*
- * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
- *
- * g.switch(*args, **kwargs)
- */
-# define PyGreenlet_Switch \
- (*(PyObject * \
- (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
- _PyGreenlet_API[PyGreenlet_Switch_NUM])
-
-/*
- * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
- *
- * g.parent = new_parent
- */
-# define PyGreenlet_SetParent \
- (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
- _PyGreenlet_API[PyGreenlet_SetParent_NUM])
-
-/*
- * PyGreenlet_GetParent(PyObject* greenlet)
- *
- * return greenlet.parent;
- *
- * This could return NULL even if there is no exception active.
- * If it does not return NULL, you are responsible for decrementing the
- * reference count.
- */
-# define PyGreenlet_GetParent \
- (*(PyGreenlet* (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
-
-/*
- * deprecated, undocumented alias.
- */
-# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
-
-# define PyGreenlet_MAIN \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_MAIN_NUM])
-
-# define PyGreenlet_STARTED \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_STARTED_NUM])
-
-# define PyGreenlet_ACTIVE \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
-
-
-
-
-/* Macro that imports greenlet and initializes C API */
-/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
- keep the older definition to be sure older code that might have a copy of
- the header still works. */
-# define PyGreenlet_Import() \
- { \
- _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
- }
-
-#endif /* GREENLET_MODULE */
-
-#ifdef __cplusplus
-}
-#endif
-#endif /* !Py_GREENLETOBJECT_H */
diff --git a/backend/.venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc
deleted file mode 100644
index 6dac04a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/_yaml/__init__.py b/backend/.venv/lib/python3.12/site-packages/_yaml/__init__.py
deleted file mode 100644
index 7baa8c4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/_yaml/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# This is a stub package designed to roughly emulate the _yaml
-# extension module, which previously existed as a standalone module
-# and has been moved into the `yaml` package namespace.
-# It does not perfectly mimic its old counterpart, but should get
-# close enough for anyone who's relying on it even when they shouldn't.
-import yaml
-
-# in some circumstances, the yaml module we imoprted may be from a different version, so we need
-# to tread carefully when poking at it here (it may not have the attributes we expect)
-if not getattr(yaml, '__with_libyaml__', False):
- from sys import version_info
-
- exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
- raise exc("No module named '_yaml'")
-else:
- from yaml._yaml import *
- import warnings
- warnings.warn(
- 'The _yaml extension module is now located at yaml._yaml'
- ' and its location is subject to change. To use the'
- ' LibYAML-based parser and emitter, import from `yaml`:'
- ' `from yaml import CLoader as Loader, CDumper as Dumper`.',
- DeprecationWarning
- )
- del warnings
- # Don't `del yaml` here because yaml is actually an existing
- # namespace member of _yaml.
-
-__name__ = '_yaml'
-# If the module is top-level (i.e. not a part of any specific package)
-# then the attribute should be set to ''.
-# https://docs.python.org/3.8/library/types.html
-__package__ = ''
diff --git a/backend/.venv/lib/python3.12/site-packages/_yaml/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/_yaml/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 8aa5c47..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/_yaml/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/METADATA
deleted file mode 100644
index 1530f70..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/METADATA
+++ /dev/null
@@ -1,139 +0,0 @@
-Metadata-Version: 2.4
-Name: alembic
-Version: 1.18.3
-Summary: A database migration tool for SQLAlchemy.
-Author-email: Mike Bayer
-License-Expression: MIT
-Project-URL: Homepage, https://alembic.sqlalchemy.org
-Project-URL: Documentation, https://alembic.sqlalchemy.org/en/latest/
-Project-URL: Changelog, https://alembic.sqlalchemy.org/en/latest/changelog.html
-Project-URL: Source, https://github.com/sqlalchemy/alembic/
-Project-URL: Issue Tracker, https://github.com/sqlalchemy/alembic/issues/
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Environment :: Console
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Topic :: Database :: Front-Ends
-Requires-Python: >=3.10
-Description-Content-Type: text/x-rst
-License-File: LICENSE
-Requires-Dist: SQLAlchemy>=1.4.23
-Requires-Dist: Mako
-Requires-Dist: typing-extensions>=4.12
-Requires-Dist: tomli; python_version < "3.11"
-Provides-Extra: tz
-Requires-Dist: tzdata; extra == "tz"
-Dynamic: license-file
-
-Alembic is a database migrations tool written by the author
-of `SQLAlchemy `_. A migrations tool
-offers the following functionality:
-
-* Can emit ALTER statements to a database in order to change
- the structure of tables and other constructs
-* Provides a system whereby "migration scripts" may be constructed;
- each script indicates a particular series of steps that can "upgrade" a
- target database to a new version, and optionally a series of steps that can
- "downgrade" similarly, doing the same steps in reverse.
-* Allows the scripts to execute in some sequential manner.
-
-The goals of Alembic are:
-
-* Very open ended and transparent configuration and operation. A new
- Alembic environment is generated from a set of templates which is selected
- among a set of options when setup first occurs. The templates then deposit a
- series of scripts that define fully how database connectivity is established
- and how migration scripts are invoked; the migration scripts themselves are
- generated from a template within that series of scripts. The scripts can
- then be further customized to define exactly how databases will be
- interacted with and what structure new migration files should take.
-* Full support for transactional DDL. The default scripts ensure that all
- migrations occur within a transaction - for those databases which support
- this (Postgresql, Microsoft SQL Server), migrations can be tested with no
- need to manually undo changes upon failure.
-* Minimalist script construction. Basic operations like renaming
- tables/columns, adding/removing columns, changing column attributes can be
- performed through one line commands like alter_column(), rename_table(),
- add_constraint(). There is no need to recreate full SQLAlchemy Table
- structures for simple operations like these - the functions themselves
- generate minimalist schema structures behind the scenes to achieve the given
- DDL sequence.
-* "auto generation" of migrations. While real world migrations are far more
- complex than what can be automatically determined, Alembic can still
- eliminate the initial grunt work in generating new migration directives
- from an altered schema. The ``--autogenerate`` feature will inspect the
- current status of a database using SQLAlchemy's schema inspection
- capabilities, compare it to the current state of the database model as
- specified in Python, and generate a series of "candidate" migrations,
- rendering them into a new migration script as Python directives. The
- developer then edits the new file, adding additional directives and data
- migrations as needed, to produce a finished migration. Table and column
- level changes can be detected, with constraints and indexes to follow as
- well.
-* Full support for migrations generated as SQL scripts. Those of us who
- work in corporate environments know that direct access to DDL commands on a
- production database is a rare privilege, and DBAs want textual SQL scripts.
- Alembic's usage model and commands are oriented towards being able to run a
- series of migrations into a textual output file as easily as it runs them
- directly to a database. Care must be taken in this mode to not invoke other
- operations that rely upon in-memory SELECTs of rows - Alembic tries to
- provide helper constructs like bulk_insert() to help with data-oriented
- operations that are compatible with script-based DDL.
-* Non-linear, dependency-graph versioning. Scripts are given UUID
- identifiers similarly to a DVCS, and the linkage of one script to the next
- is achieved via human-editable markers within the scripts themselves.
- The structure of a set of migration files is considered as a
- directed-acyclic graph, meaning any migration file can be dependent
- on any other arbitrary set of migration files, or none at
- all. Through this open-ended system, migration files can be organized
- into branches, multiple roots, and mergepoints, without restriction.
- Commands are provided to produce new branches, roots, and merges of
- branches automatically.
-* Provide a library of ALTER constructs that can be used by any SQLAlchemy
- application. The DDL constructs build upon SQLAlchemy's own DDLElement base
- and can be used standalone by any application or script.
-* At long last, bring SQLite and its inability to ALTER things into the fold,
- but in such a way that SQLite's very special workflow needs are accommodated
- in an explicit way that makes the most of a bad situation, through the
- concept of a "batch" migration, where multiple changes to a table can
- be batched together to form a series of instructions for a single, subsequent
- "move-and-copy" workflow. You can even use "move-and-copy" workflow for
- other databases, if you want to recreate a table in the background
- on a busy system.
-
-Documentation and status of Alembic is at https://alembic.sqlalchemy.org/
-
-The SQLAlchemy Project
-======================
-
-Alembic is part of the `SQLAlchemy Project `_ and
-adheres to the same standards and conventions as the core project.
-
-Development / Bug reporting / Pull requests
-___________________________________________
-
-Please refer to the
-`SQLAlchemy Community Guide `_ for
-guidelines on coding and participating in this project.
-
-Code of Conduct
-_______________
-
-Above all, SQLAlchemy places great emphasis on polite, thoughtful, and
-constructive communication between users and developers.
-Please see our current Code of Conduct at
-`Code of Conduct `_.
-
-License
-=======
-
-Alembic is distributed under the `MIT license
-`_.
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/RECORD
deleted file mode 100644
index 5a11df8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/RECORD
+++ /dev/null
@@ -1,179 +0,0 @@
-../../../bin/alembic,sha256=JutC7mxPM5RNS9OR_GTIgI7duce15nk7SgmejuiirBw,216
-alembic-1.18.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-alembic-1.18.3.dist-info/METADATA,sha256=ki8BgachiHEpcRIeva-QZchAy_hlN4gRPPZPBaV20fQ,7217
-alembic-1.18.3.dist-info/RECORD,,
-alembic-1.18.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-alembic-1.18.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
-alembic-1.18.3.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48
-alembic-1.18.3.dist-info/licenses/LICENSE,sha256=bmjZSgOg4-Mn3fPobR6-3BTuzjkiAiYY_CRqNilv0Mw,1059
-alembic-1.18.3.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8
-alembic/__init__.py,sha256=z4eicue_VddRY1zr7UrX5AxqVHdejipCJ_GeKO6QGlM,93
-alembic/__main__.py,sha256=373m7-TBh72JqrSMYviGrxCHZo-cnweM8AGF8A22PmY,78
-alembic/__pycache__/__init__.cpython-312.pyc,,
-alembic/__pycache__/__main__.cpython-312.pyc,,
-alembic/__pycache__/command.cpython-312.pyc,,
-alembic/__pycache__/config.cpython-312.pyc,,
-alembic/__pycache__/context.cpython-312.pyc,,
-alembic/__pycache__/environment.cpython-312.pyc,,
-alembic/__pycache__/migration.cpython-312.pyc,,
-alembic/__pycache__/op.cpython-312.pyc,,
-alembic/autogenerate/__init__.py,sha256=ntmUTXhjLm4_zmqIwyVaECdpPDn6_u1yM9vYk6-553E,543
-alembic/autogenerate/__pycache__/__init__.cpython-312.pyc,,
-alembic/autogenerate/__pycache__/api.cpython-312.pyc,,
-alembic/autogenerate/__pycache__/render.cpython-312.pyc,,
-alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc,,
-alembic/autogenerate/api.py,sha256=8tVNDSHlqsBgj1IVLdqvZr_jlvz9kp3O5EKIL9biaZg,22781
-alembic/autogenerate/compare/__init__.py,sha256=kCvA0ZK0rTahNv9wlgyIB5DH2lFEhTRO4PFmoqcL9JE,1809
-alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc,,
-alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc,,
-alembic/autogenerate/compare/comments.py,sha256=agSrWsZhJ47i-E-EqiP3id2CXTTbP0muOKk1-9in9lg,3234
-alembic/autogenerate/compare/constraints.py,sha256=7sLSvUK9M2CbMRRQy5pveIXbjDLRDnfPx0Dvi_KXOf8,27906
-alembic/autogenerate/compare/schema.py,sha256=plQ7JJ1zJGlnajweSV8lAD9tDYPks5G40sliocTuXJA,1695
-alembic/autogenerate/compare/server_defaults.py,sha256=D--5EvEfyX0fSVkK6iLtRoer5sYK6xeNC2TIdu7klUk,10792
-alembic/autogenerate/compare/tables.py,sha256=47pAgVhbmXGLrm3dMK6hrNABxOAe_cGSQmPtCBwORVc,10611
-alembic/autogenerate/compare/types.py,sha256=75bOduz-dOiyLI065XD5sEP_JF9GPLkDAQ_y5B8lXF0,4005
-alembic/autogenerate/compare/util.py,sha256=K_GArJ2xQXZi6ftb8gkgZuIdVqvyep3E2ZXq8F3-jIU,9521
-alembic/autogenerate/render.py,sha256=ceQL8nk8m2kBtQq5gtxtDLR9iR0Sck8xG_61Oez-Sqs,37270
-alembic/autogenerate/rewriter.py,sha256=NIASSS-KaNKPmbm1k4pE45aawwjSh1Acf6eZrOwnUGM,7814
-alembic/command.py,sha256=7RzAwwXR31sOl0oVItyZl9B0j3TeR5dRyx9634lVsLM,25297
-alembic/config.py,sha256=VoCZV2cFZoF0Xa1OxHqsA-MKzuwBRaJSC7hxZ3-uWN4,34983
-alembic/context.py,sha256=hK1AJOQXJ29Bhn276GYcosxeG7pC5aZRT5E8c4bMJ4Q,195
-alembic/context.pyi,sha256=b_naI_W8dyiZRsL_n299a-LbqLZxKTAgDIXubRLVKlY,32531
-alembic/ddl/__init__.py,sha256=Df8fy4Vn_abP8B7q3x8gyFwEwnLw6hs2Ljt_bV3EZWE,152
-alembic/ddl/__pycache__/__init__.cpython-312.pyc,,
-alembic/ddl/__pycache__/_autogen.cpython-312.pyc,,
-alembic/ddl/__pycache__/base.cpython-312.pyc,,
-alembic/ddl/__pycache__/impl.cpython-312.pyc,,
-alembic/ddl/__pycache__/mssql.cpython-312.pyc,,
-alembic/ddl/__pycache__/mysql.cpython-312.pyc,,
-alembic/ddl/__pycache__/oracle.cpython-312.pyc,,
-alembic/ddl/__pycache__/postgresql.cpython-312.pyc,,
-alembic/ddl/__pycache__/sqlite.cpython-312.pyc,,
-alembic/ddl/_autogen.py,sha256=Blv2RrHNyF4cE6znCQXNXG5T9aO-YmiwD4Fz-qfoaWA,9275
-alembic/ddl/base.py,sha256=-EJR1czafLCs4fJxWlYlfYGXe98nST52owFT_2-xYKU,11744
-alembic/ddl/impl.py,sha256=lQfpmV9xxG4_YMnSMg0nwd3IqvdZ65a2efeedCGQdp8,31270
-alembic/ddl/mssql.py,sha256=dee0acwnxmTZXuYPqqlYaDiSbKS46zVH0WRULjX5Blg,17398
-alembic/ddl/mysql.py,sha256=2fvzGcdg4qqCJogGnzvQN636vUi9mF6IoQWLGevvF_A,18456
-alembic/ddl/oracle.py,sha256=669YlkcZihlXFbnXhH2krdrvDry8q5pcUGfoqkg_R6Y,6243
-alembic/ddl/postgresql.py,sha256=04M4OpZOCJJ3ipuHoVwlR1gI1sgRwOguRRVx_mFg8Uc,30417
-alembic/ddl/sqlite.py,sha256=TmzU3YaR3aw_0spSrA6kcUY8fyDfwsu4GkH5deYPEK8,8017
-alembic/environment.py,sha256=MM5lPayGT04H3aeng1H7GQ8HEAs3VGX5yy6mDLCPLT4,43
-alembic/migration.py,sha256=MV6Fju6rZtn2fTREKzXrCZM6aIBGII4OMZFix0X-GLs,41
-alembic/op.py,sha256=flHtcsVqOD-ZgZKK2pv-CJ5Cwh-KJ7puMUNXzishxLw,167
-alembic/op.pyi,sha256=Dq_F-YdIpqJyUkggeZtU2TXsK76KAxoJSN20c6Rlw64,51427
-alembic/operations/__init__.py,sha256=e0KQSZAgLpTWvyvreB7DWg7RJV_MWSOPVDgCqsd2FzY,318
-alembic/operations/__pycache__/__init__.cpython-312.pyc,,
-alembic/operations/__pycache__/base.cpython-312.pyc,,
-alembic/operations/__pycache__/batch.cpython-312.pyc,,
-alembic/operations/__pycache__/ops.cpython-312.pyc,,
-alembic/operations/__pycache__/schemaobj.cpython-312.pyc,,
-alembic/operations/__pycache__/toimpl.cpython-312.pyc,,
-alembic/operations/base.py,sha256=iYNlJpGWChgbfy90ei3Bcxk-8rtV1ZaYn9cgSGm_PF4,76161
-alembic/operations/batch.py,sha256=hYOpzG2FK_8hk-rHNuLuFAA3-VXRSOnsTrpz2YlA61Q,26947
-alembic/operations/ops.py,sha256=V0p0qy3zmIVqO1SHmpvYkI7X7ziqPgrvq29QNa4_hmw,97000
-alembic/operations/schemaobj.py,sha256=Wp-bBe4a8lXPTvIHJttBY0ejtpVR5Jvtb2kI-U2PztQ,9468
-alembic/operations/toimpl.py,sha256=qyCzZ6j-2jvjZDAR6Z-DdR3-Gxy1spZE9bHOdQzp6lo,8365
-alembic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-alembic/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-alembic/runtime/__pycache__/__init__.cpython-312.pyc,,
-alembic/runtime/__pycache__/environment.cpython-312.pyc,,
-alembic/runtime/__pycache__/migration.cpython-312.pyc,,
-alembic/runtime/__pycache__/plugins.cpython-312.pyc,,
-alembic/runtime/environment.py,sha256=1cR1v18sIKvOPZMlc4fHGU4J8r6Dec9h4o3WXkMmFKQ,42400
-alembic/runtime/migration.py,sha256=mR2Ee1h9Yy6OMFeDL4LOYorLYby2l2f899WGK_boECw,48427
-alembic/runtime/plugins.py,sha256=pWCDhMX8MvR8scXhiGSRNYNW7-ckEbOW2qK58xRFy1Q,5707
-alembic/script/__init__.py,sha256=lSj06O391Iy5avWAiq8SPs6N8RBgxkSPjP8wpXcNDGg,100
-alembic/script/__pycache__/__init__.cpython-312.pyc,,
-alembic/script/__pycache__/base.cpython-312.pyc,,
-alembic/script/__pycache__/revision.cpython-312.pyc,,
-alembic/script/__pycache__/write_hooks.cpython-312.pyc,,
-alembic/script/base.py,sha256=OInSjbfcnUSjVCc5vVYY33UJ1Uo5xE5Huicp8P9VM1I,36698
-alembic/script/revision.py,sha256=SEePZPTMIyfjF73QAD0VIax9jc1dALkiLQZwTzwiyPw,62312
-alembic/script/write_hooks.py,sha256=KWH12250h_JcdBkGsLVo9JKYKpNcJxBUjwZ9r_r88Bc,5369
-alembic/templates/async/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58
-alembic/templates/async/__pycache__/env.cpython-312.pyc,,
-alembic/templates/async/alembic.ini.mako,sha256=esbuCnpkyjntJC7k9NnYcCAzhrRQ8NVC4pWineiRk_w,5010
-alembic/templates/async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389
-alembic/templates/async/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
-alembic/templates/generic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
-alembic/templates/generic/__pycache__/env.cpython-312.pyc,,
-alembic/templates/generic/alembic.ini.mako,sha256=2i2vPsGQSmE9XMiLz8tSBF_UIA8PJl0-fAvbRVmiK_w,5010
-alembic/templates/generic/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103
-alembic/templates/generic/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
-alembic/templates/multidb/README,sha256=dWLDhnBgphA4Nzb7sNlMfCS3_06YqVbHhz-9O5JNqyI,606
-alembic/templates/multidb/__pycache__/env.cpython-312.pyc,,
-alembic/templates/multidb/alembic.ini.mako,sha256=asVt3aJVwjuuw9bopfMofVvonO31coXBbV5DeMRN6cM,5336
-alembic/templates/multidb/env.py,sha256=6zNjnW8mXGUk7erTsAvrfhvqoczJ-gagjVq1Ypg2YIQ,4230
-alembic/templates/multidb/script.py.mako,sha256=ZbCXMkI5Wj2dwNKcxuVGkKZ7Iav93BNx_bM4zbGi3c8,1235
-alembic/templates/pyproject/README,sha256=dMhIiFoeM7EdeaOXBs3mVQ6zXACMyGXDb_UBB6sGRA0,60
-alembic/templates/pyproject/__pycache__/env.cpython-312.pyc,,
-alembic/templates/pyproject/alembic.ini.mako,sha256=bQnEoydnLOUgg9vNbTOys4r5MaW8lmwYFXSrlfdEEkw,782
-alembic/templates/pyproject/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103
-alembic/templates/pyproject/pyproject.toml.mako,sha256=W6x_K-xLfEvyM8D4B3Fg0l20P1h6SPK33188pqRFroQ,3000
-alembic/templates/pyproject/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
-alembic/templates/pyproject_async/README,sha256=2Q5XcEouiqQ-TJssO9805LROkVUd0F6d74rTnuLrifA,45
-alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc,,
-alembic/templates/pyproject_async/alembic.ini.mako,sha256=bQnEoydnLOUgg9vNbTOys4r5MaW8lmwYFXSrlfdEEkw,782
-alembic/templates/pyproject_async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389
-alembic/templates/pyproject_async/pyproject.toml.mako,sha256=W6x_K-xLfEvyM8D4B3Fg0l20P1h6SPK33188pqRFroQ,3000
-alembic/templates/pyproject_async/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704
-alembic/testing/__init__.py,sha256=PTMhi_2PZ1T_3atQS2CIr0V4YRZzx_doKI-DxKdQS44,1297
-alembic/testing/__pycache__/__init__.cpython-312.pyc,,
-alembic/testing/__pycache__/assertions.cpython-312.pyc,,
-alembic/testing/__pycache__/env.cpython-312.pyc,,
-alembic/testing/__pycache__/fixtures.cpython-312.pyc,,
-alembic/testing/__pycache__/requirements.cpython-312.pyc,,
-alembic/testing/__pycache__/schemacompare.cpython-312.pyc,,
-alembic/testing/__pycache__/util.cpython-312.pyc,,
-alembic/testing/__pycache__/warnings.cpython-312.pyc,,
-alembic/testing/assertions.py,sha256=VKXMEVWjuPAsYnNxP3WnUpXaFN3ytNFf9LI72OEJ074,5344
-alembic/testing/env.py,sha256=oQN56xXHtHfK8RD-8pH8yZ-uWcjpuNL1Mt5HNrzZyc0,12151
-alembic/testing/fixtures.py,sha256=meqm10rd1ynppW6tw1wcpDJJLyQezZ7FwKyqcrwIOok,11931
-alembic/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc,,
-alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc,,
-alembic/testing/plugin/bootstrap.py,sha256=9C6wtjGrIVztZ928w27hsQE0KcjDLIUtUN3dvZKsMVk,50
-alembic/testing/requirements.py,sha256=OZSHd8I3zOb7288cZxUTebqxx8j0T6I8MekH15TyPvY,4566
-alembic/testing/schemacompare.py,sha256=N5UqSNCOJetIKC4vKhpYzQEpj08XkdgIoqBmEPQ3tlc,4838
-alembic/testing/suite/__init__.py,sha256=MvE7-hwbaVN1q3NM-ztGxORU9dnIelUCINKqNxewn7Y,288
-alembic/testing/suite/__pycache__/__init__.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc,,
-alembic/testing/suite/__pycache__/test_op.cpython-312.pyc,,
-alembic/testing/suite/_autogen_fixtures.py,sha256=3nNTd8iDeVeSgpPIj8KAraNbU-PkJtxDb4X_TVsZ528,14200
-alembic/testing/suite/test_autogen_comments.py,sha256=aEGqKUDw4kHjnDk298aoGcQvXJWmZXcIX_2FxH4cJK8,6283
-alembic/testing/suite/test_autogen_computed.py,sha256=puJ0hBtLzNz8LiPGqDPS8vse6dUS9VCBpUdw-cOksZo,4554
-alembic/testing/suite/test_autogen_diffs.py,sha256=T4SR1n_kmcOKYhR4W1-dA0e5sddJ69DSVL2HW96kAkE,8394
-alembic/testing/suite/test_autogen_fks.py,sha256=wHKjD4Egf7IZlH0HYw-c8uti0jhJpOm5K42QMXf5tIw,32930
-alembic/testing/suite/test_autogen_identity.py,sha256=kcuqngG7qXAKPJDX4U8sRzPKHEJECHuZ0DtuaS6tVkk,5824
-alembic/testing/suite/test_environment.py,sha256=OwD-kpESdLoc4byBrGrXbZHvqtPbzhFCG4W9hJOJXPQ,11877
-alembic/testing/suite/test_op.py,sha256=2XQCdm_NmnPxHGuGj7hmxMzIhKxXNotUsKdACXzE1mM,1343
-alembic/testing/util.py,sha256=CQrcQDA8fs_7ME85z5ydb-Bt70soIIID-qNY1vbR2dg,3350
-alembic/testing/warnings.py,sha256=cDDWzvxNZE6x9dME2ACTXSv01G81JcIbE1GIE_s1kvg,831
-alembic/util/__init__.py,sha256=xNpZtajyTF4eVEbLj0Pcm2FbNkIZD_pCvKGKSPucTEs,1777
-alembic/util/__pycache__/__init__.cpython-312.pyc,,
-alembic/util/__pycache__/compat.cpython-312.pyc,,
-alembic/util/__pycache__/editor.cpython-312.pyc,,
-alembic/util/__pycache__/exc.cpython-312.pyc,,
-alembic/util/__pycache__/langhelpers.cpython-312.pyc,,
-alembic/util/__pycache__/messaging.cpython-312.pyc,,
-alembic/util/__pycache__/pyfiles.cpython-312.pyc,,
-alembic/util/__pycache__/sqla_compat.cpython-312.pyc,,
-alembic/util/compat.py,sha256=NytmcsMtK8WEEVwWc-ZWYlSOi55BtRlmJXjxnF3nsh8,3810
-alembic/util/editor.py,sha256=JIz6_BdgV8_oKtnheR6DZoB7qnrHrlRgWjx09AsTsUw,2546
-alembic/util/exc.py,sha256=SublpLmAeAW8JeEml-1YyhIjkSORTkZbvHVVJeoPymg,993
-alembic/util/langhelpers.py,sha256=GBbR01xNi1kmz8W37h0NzXl3hBC1SY7k7Bj-h5jVgps,13164
-alembic/util/messaging.py,sha256=3bEBoDy4EAXETXAvArlYjeMITXDTgPTu6ZoE3ytnzSw,3294
-alembic/util/pyfiles.py,sha256=QUZYc5kE3Z7nV64PblcRffzA7VfVaiFB2x3vtcG0_AE,4707
-alembic/util/sqla_compat.py,sha256=llgJVtOsO1c3euS9_peORZkM9QeSvQWa-1LNHqrzEM4,15246
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/REQUESTED b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/WHEEL
deleted file mode 100644
index 0885d05..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: setuptools (80.10.2)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/entry_points.txt b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/entry_points.txt
deleted file mode 100644
index 5945268..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/entry_points.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[console_scripts]
-alembic = alembic.config:main
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/licenses/LICENSE
deleted file mode 100644
index b03e235..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright 2009-2026 Michael Bayer.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/top_level.txt b/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/top_level.txt
deleted file mode 100644
index b5bd98d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic-1.18.3.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-alembic
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/__init__.py
deleted file mode 100644
index b63cc6b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from . import context
-from . import op
-from .runtime import plugins
-
-
-__version__ = "1.18.3"
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__main__.py b/backend/.venv/lib/python3.12/site-packages/alembic/__main__.py
deleted file mode 100644
index af1b8e8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/__main__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .config import main
-
-if __name__ == "__main__":
- main(prog="alembic")
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index b9baf7f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__main__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__main__.cpython-312.pyc
deleted file mode 100644
index 6276299..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/__main__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc
deleted file mode 100644
index a98c93c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc
deleted file mode 100644
index 7030045..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc
deleted file mode 100644
index 3ff03f7..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc
deleted file mode 100644
index 7596f7f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc
deleted file mode 100644
index 0e8cd32..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc
deleted file mode 100644
index 206cac8..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__init__.py
deleted file mode 100644
index 445ddb2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from .api import _render_migration_diffs as _render_migration_diffs
-from .api import compare_metadata as compare_metadata
-from .api import produce_migrations as produce_migrations
-from .api import render_python_code as render_python_code
-from .api import RevisionContext as RevisionContext
-from .compare import _produce_net_changes as _produce_net_changes
-from .compare import comparators as comparators
-from .render import render_op_text as render_op_text
-from .render import renderers as renderers
-from .rewriter import Rewriter as Rewriter
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index b493b6d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc
deleted file mode 100644
index 213e7c7..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc
deleted file mode 100644
index e06cf52..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc
deleted file mode 100644
index 185d4b9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/api.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/api.py
deleted file mode 100644
index b2e3fae..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/api.py
+++ /dev/null
@@ -1,667 +0,0 @@
-from __future__ import annotations
-
-import contextlib
-import logging
-from typing import Any
-from typing import Dict
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import inspect
-
-from . import compare
-from . import render
-from .. import util
-from ..operations import ops
-from ..runtime.plugins import Plugin
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from sqlalchemy.engine import Connection
- from sqlalchemy.engine import Dialect
- from sqlalchemy.engine import Inspector
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.sql.schema import Table
-
- from ..config import Config
- from ..operations.ops import DowngradeOps
- from ..operations.ops import MigrationScript
- from ..operations.ops import UpgradeOps
- from ..runtime.environment import NameFilterParentNames
- from ..runtime.environment import NameFilterType
- from ..runtime.environment import ProcessRevisionDirectiveFn
- from ..runtime.environment import RenderItemFn
- from ..runtime.migration import MigrationContext
- from ..script.base import Script
- from ..script.base import ScriptDirectory
- from ..script.revision import _GetRevArg
- from ..util import PriorityDispatcher
-
-
-log = logging.getLogger(__name__)
-
-
-def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any:
- """Compare a database schema to that given in a
- :class:`~sqlalchemy.schema.MetaData` instance.
-
- The database connection is presented in the context
- of a :class:`.MigrationContext` object, which
- provides database connectivity as well as optional
- comparison functions to use for datatypes and
- server defaults - see the "autogenerate" arguments
- at :meth:`.EnvironmentContext.configure`
- for details on these.
-
- The return format is a list of "diff" directives,
- each representing individual differences::
-
- from alembic.migration import MigrationContext
- from alembic.autogenerate import compare_metadata
- from sqlalchemy import (
- create_engine,
- MetaData,
- Column,
- Integer,
- String,
- Table,
- text,
- )
- import pprint
-
- engine = create_engine("sqlite://")
-
- with engine.begin() as conn:
- conn.execute(
- text(
- '''
- create table foo (
- id integer not null primary key,
- old_data varchar,
- x integer
- )
- '''
- )
- )
- conn.execute(text("create table bar (data varchar)"))
-
- metadata = MetaData()
- Table(
- "foo",
- metadata,
- Column("id", Integer, primary_key=True),
- Column("data", Integer),
- Column("x", Integer, nullable=False),
- )
- Table("bat", metadata, Column("info", String))
-
- mc = MigrationContext.configure(engine.connect())
-
- diff = compare_metadata(mc, metadata)
- pprint.pprint(diff, indent=2, width=20)
-
- Output::
-
- [
- (
- "add_table",
- Table(
- "bat",
- MetaData(),
- Column("info", String(), table=),
- schema=None,
- ),
- ),
- (
- "remove_table",
- Table(
- "bar",
- MetaData(),
- Column("data", VARCHAR(), table=),
- schema=None,
- ),
- ),
- (
- "add_column",
- None,
- "foo",
- Column("data", Integer(), table=),
- ),
- [
- (
- "modify_nullable",
- None,
- "foo",
- "x",
- {
- "existing_comment": None,
- "existing_server_default": False,
- "existing_type": INTEGER(),
- },
- True,
- False,
- )
- ],
- (
- "remove_column",
- None,
- "foo",
- Column("old_data", VARCHAR(), table=),
- ),
- ]
-
- :param context: a :class:`.MigrationContext`
- instance.
- :param metadata: a :class:`~sqlalchemy.schema.MetaData`
- instance.
-
- .. seealso::
-
- :func:`.produce_migrations` - produces a :class:`.MigrationScript`
- structure based on metadata comparison.
-
- """
-
- migration_script = produce_migrations(context, metadata)
- assert migration_script.upgrade_ops is not None
- return migration_script.upgrade_ops.as_diffs()
-
-
-def produce_migrations(
- context: MigrationContext, metadata: MetaData
-) -> MigrationScript:
- """Produce a :class:`.MigrationScript` structure based on schema
- comparison.
-
- This function does essentially what :func:`.compare_metadata` does,
- but then runs the resulting list of diffs to produce the full
- :class:`.MigrationScript` object. For an example of what this looks like,
- see the example in :ref:`customizing_revision`.
-
- .. seealso::
-
- :func:`.compare_metadata` - returns more fundamental "diff"
- data from comparing a schema.
-
- """
-
- autogen_context = AutogenContext(context, metadata=metadata)
-
- migration_script = ops.MigrationScript(
- rev_id=None,
- upgrade_ops=ops.UpgradeOps([]),
- downgrade_ops=ops.DowngradeOps([]),
- )
-
- compare._populate_migration_script(autogen_context, migration_script)
-
- return migration_script
-
-
-def render_python_code(
- up_or_down_op: Union[UpgradeOps, DowngradeOps],
- sqlalchemy_module_prefix: str = "sa.",
- alembic_module_prefix: str = "op.",
- render_as_batch: bool = False,
- imports: Sequence[str] = (),
- render_item: Optional[RenderItemFn] = None,
- migration_context: Optional[MigrationContext] = None,
- user_module_prefix: Optional[str] = None,
-) -> str:
- """Render Python code given an :class:`.UpgradeOps` or
- :class:`.DowngradeOps` object.
-
- This is a convenience function that can be used to test the
- autogenerate output of a user-defined :class:`.MigrationScript` structure.
-
- :param up_or_down_op: :class:`.UpgradeOps` or :class:`.DowngradeOps` object
- :param sqlalchemy_module_prefix: module prefix for SQLAlchemy objects
- :param alembic_module_prefix: module prefix for Alembic constructs
- :param render_as_batch: use "batch operations" style for rendering
- :param imports: sequence of import symbols to add
- :param render_item: callable to render items
- :param migration_context: optional :class:`.MigrationContext`
- :param user_module_prefix: optional string prefix for user-defined types
-
- .. versionadded:: 1.11.0
-
- """
- opts = {
- "sqlalchemy_module_prefix": sqlalchemy_module_prefix,
- "alembic_module_prefix": alembic_module_prefix,
- "render_item": render_item,
- "render_as_batch": render_as_batch,
- "user_module_prefix": user_module_prefix,
- }
-
- if migration_context is None:
- from ..runtime.migration import MigrationContext
- from sqlalchemy.engine.default import DefaultDialect
-
- migration_context = MigrationContext.configure(
- dialect=DefaultDialect()
- )
-
- autogen_context = AutogenContext(migration_context, opts=opts)
- autogen_context.imports = set(imports)
- return render._indent(
- render._render_cmd_body(up_or_down_op, autogen_context)
- )
-
-
-def _render_migration_diffs(
- context: MigrationContext, template_args: Dict[Any, Any]
-) -> None:
- """legacy, used by test_autogen_composition at the moment"""
-
- autogen_context = AutogenContext(context)
-
- upgrade_ops = ops.UpgradeOps([])
- compare._produce_net_changes(autogen_context, upgrade_ops)
-
- migration_script = ops.MigrationScript(
- rev_id=None,
- upgrade_ops=upgrade_ops,
- downgrade_ops=upgrade_ops.reverse(),
- )
-
- render._render_python_into_templatevars(
- autogen_context, migration_script, template_args
- )
-
-
-class AutogenContext:
- """Maintains configuration and state that's specific to an
- autogenerate operation."""
-
- metadata: Union[MetaData, Sequence[MetaData], None] = None
- """The :class:`~sqlalchemy.schema.MetaData` object
- representing the destination.
-
- This object is the one that is passed within ``env.py``
- to the :paramref:`.EnvironmentContext.configure.target_metadata`
- parameter. It represents the structure of :class:`.Table` and other
- objects as stated in the current database model, and represents the
- destination structure for the database being examined.
-
- While the :class:`~sqlalchemy.schema.MetaData` object is primarily
- known as a collection of :class:`~sqlalchemy.schema.Table` objects,
- it also has an :attr:`~sqlalchemy.schema.MetaData.info` dictionary
- that may be used by end-user schemes to store additional schema-level
- objects that are to be compared in custom autogeneration schemes.
-
- """
-
- connection: Optional[Connection] = None
- """The :class:`~sqlalchemy.engine.base.Connection` object currently
- connected to the database backend being compared.
-
- This is obtained from the :attr:`.MigrationContext.bind` and is
- ultimately set up in the ``env.py`` script.
-
- """
-
- dialect: Dialect
- """The :class:`~sqlalchemy.engine.Dialect` object currently in use.
-
- This is normally obtained from the
- :attr:`~sqlalchemy.engine.base.Connection.dialect` attribute.
-
- """
-
- imports: Set[str] = None # type: ignore[assignment]
- """A ``set()`` which contains string Python import directives.
-
- The directives are to be rendered into the ``${imports}`` section
- of a script template. The set is normally empty and can be modified
- within hooks such as the
- :paramref:`.EnvironmentContext.configure.render_item` hook.
-
- .. seealso::
-
- :ref:`autogen_render_types`
-
- """
-
- migration_context: MigrationContext
- """The :class:`.MigrationContext` established by the ``env.py`` script."""
-
- comparators: PriorityDispatcher
-
- def __init__(
- self,
- migration_context: MigrationContext,
- metadata: Union[MetaData, Sequence[MetaData], None] = None,
- opts: Optional[Dict[str, Any]] = None,
- autogenerate: bool = True,
- ) -> None:
- if (
- autogenerate
- and migration_context is not None
- and migration_context.as_sql
- ):
- raise util.CommandError(
- "autogenerate can't use as_sql=True as it prevents querying "
- "the database for schema information"
- )
-
- # branch off from the "global" comparators. This collection
- # is empty in Alembic except that it is populated by third party
- # extensions that don't use the plugin system. so we will build
- # off of whatever is in there.
- if autogenerate:
- self.comparators = compare.comparators.branch()
- Plugin.populate_autogenerate_priority_dispatch(
- self.comparators,
- include_plugins=migration_context.opts.get(
- "autogenerate_plugins", ["alembic.autogenerate.*"]
- ),
- )
-
- if opts is None:
- opts = migration_context.opts
-
- self.metadata = metadata = (
- opts.get("target_metadata", None) if metadata is None else metadata
- )
-
- if (
- autogenerate
- and metadata is None
- and migration_context is not None
- and migration_context.script is not None
- ):
- raise util.CommandError(
- "Can't proceed with --autogenerate option; environment "
- "script %s does not provide "
- "a MetaData object or sequence of objects to the context."
- % (migration_context.script.env_py_location)
- )
-
- include_object = opts.get("include_object", None)
- include_name = opts.get("include_name", None)
-
- object_filters = []
- name_filters = []
- if include_object:
- object_filters.append(include_object)
- if include_name:
- name_filters.append(include_name)
-
- self._object_filters = object_filters
- self._name_filters = name_filters
-
- self.migration_context = migration_context
- self.connection = self.migration_context.bind
- self.dialect = self.migration_context.dialect
-
- self.imports = set()
- self.opts: Dict[str, Any] = opts
- self._has_batch: bool = False
-
- @util.memoized_property
- def inspector(self) -> Inspector:
- if self.connection is None:
- raise TypeError(
- "can't return inspector as this "
- "AutogenContext has no database connection"
- )
- return inspect(self.connection)
-
- @contextlib.contextmanager
- def _within_batch(self) -> Iterator[None]:
- self._has_batch = True
- yield
- self._has_batch = False
-
- def run_name_filters(
- self,
- name: Optional[str],
- type_: NameFilterType,
- parent_names: NameFilterParentNames,
- ) -> bool:
- """Run the context's name filters and return True if the targets
- should be part of the autogenerate operation.
-
- This method should be run for every kind of name encountered within the
- reflection side of an autogenerate operation, giving the environment
- the chance to filter what names should be reflected as database
- objects. The filters here are produced directly via the
- :paramref:`.EnvironmentContext.configure.include_name` parameter.
-
- """
- if "schema_name" in parent_names:
- if type_ == "table":
- table_name = name
- else:
- table_name = parent_names.get("table_name", None)
- if table_name:
- schema_name = parent_names["schema_name"]
- if schema_name:
- parent_names["schema_qualified_table_name"] = "%s.%s" % (
- schema_name,
- table_name,
- )
- else:
- parent_names["schema_qualified_table_name"] = table_name
-
- for fn in self._name_filters:
- if not fn(name, type_, parent_names):
- return False
- else:
- return True
-
- def run_object_filters(
- self,
- object_: SchemaItem,
- name: sqla_compat._ConstraintName,
- type_: NameFilterType,
- reflected: bool,
- compare_to: Optional[SchemaItem],
- ) -> bool:
- """Run the context's object filters and return True if the targets
- should be part of the autogenerate operation.
-
- This method should be run for every kind of object encountered within
- an autogenerate operation, giving the environment the chance
- to filter what objects should be included in the comparison.
- The filters here are produced directly via the
- :paramref:`.EnvironmentContext.configure.include_object` parameter.
-
- """
- for fn in self._object_filters:
- if not fn(object_, name, type_, reflected, compare_to):
- return False
- else:
- return True
-
- run_filters = run_object_filters
-
- @util.memoized_property
- def sorted_tables(self) -> List[Table]:
- """Return an aggregate of the :attr:`.MetaData.sorted_tables`
- collection(s).
-
- For a sequence of :class:`.MetaData` objects, this
- concatenates the :attr:`.MetaData.sorted_tables` collection
- for each individual :class:`.MetaData` in the order of the
- sequence. It does **not** collate the sorted tables collections.
-
- """
- result = []
- for m in util.to_list(self.metadata):
- result.extend(m.sorted_tables)
- return result
-
- @util.memoized_property
- def table_key_to_table(self) -> Dict[str, Table]:
- """Return an aggregate of the :attr:`.MetaData.tables` dictionaries.
-
- The :attr:`.MetaData.tables` collection is a dictionary of table key
- to :class:`.Table`; this method aggregates the dictionary across
- multiple :class:`.MetaData` objects into one dictionary.
-
- Duplicate table keys are **not** supported; if two :class:`.MetaData`
- objects contain the same table key, an exception is raised.
-
- """
- result: Dict[str, Table] = {}
- for m in util.to_list(self.metadata):
- intersect = set(result).intersection(set(m.tables))
- if intersect:
- raise ValueError(
- "Duplicate table keys across multiple "
- "MetaData objects: %s"
- % (", ".join('"%s"' % key for key in sorted(intersect)))
- )
-
- result.update(m.tables)
- return result
-
-
-class RevisionContext:
- """Maintains configuration and state that's specific to a revision
- file generation operation."""
-
- generated_revisions: List[MigrationScript]
- process_revision_directives: Optional[ProcessRevisionDirectiveFn]
-
- def __init__(
- self,
- config: Config,
- script_directory: ScriptDirectory,
- command_args: Dict[str, Any],
- process_revision_directives: Optional[
- ProcessRevisionDirectiveFn
- ] = None,
- ) -> None:
- self.config = config
- self.script_directory = script_directory
- self.command_args = command_args
- self.process_revision_directives = process_revision_directives
- self.template_args = {
- "config": config # Let templates use config for
- # e.g. multiple databases
- }
- self.generated_revisions = [self._default_revision()]
-
- def _to_script(
- self, migration_script: MigrationScript
- ) -> Optional[Script]:
- template_args: Dict[str, Any] = self.template_args.copy()
-
- if getattr(migration_script, "_needs_render", False):
- autogen_context = self._last_autogen_context
-
- # clear out existing imports if we are doing multiple
- # renders
- autogen_context.imports = set()
- if migration_script.imports:
- autogen_context.imports.update(migration_script.imports)
- render._render_python_into_templatevars(
- autogen_context, migration_script, template_args
- )
-
- assert migration_script.rev_id is not None
- return self.script_directory.generate_revision(
- migration_script.rev_id,
- migration_script.message,
- refresh=True,
- head=migration_script.head,
- splice=migration_script.splice,
- branch_labels=migration_script.branch_label,
- version_path=migration_script.version_path,
- depends_on=migration_script.depends_on,
- **template_args,
- )
-
- def run_autogenerate(
- self, rev: _GetRevArg, migration_context: MigrationContext
- ) -> None:
- self._run_environment(rev, migration_context, True)
-
- def run_no_autogenerate(
- self, rev: _GetRevArg, migration_context: MigrationContext
- ) -> None:
- self._run_environment(rev, migration_context, False)
-
- def _run_environment(
- self,
- rev: _GetRevArg,
- migration_context: MigrationContext,
- autogenerate: bool,
- ) -> None:
- if autogenerate:
- if self.command_args["sql"]:
- raise util.CommandError(
- "Using --sql with --autogenerate does not make any sense"
- )
- if set(self.script_directory.get_revisions(rev)) != set(
- self.script_directory.get_revisions("heads")
- ):
- raise util.CommandError("Target database is not up to date.")
-
- upgrade_token = migration_context.opts["upgrade_token"]
- downgrade_token = migration_context.opts["downgrade_token"]
-
- migration_script = self.generated_revisions[-1]
- if not getattr(migration_script, "_needs_render", False):
- migration_script.upgrade_ops_list[-1].upgrade_token = upgrade_token
- migration_script.downgrade_ops_list[-1].downgrade_token = (
- downgrade_token
- )
- migration_script._needs_render = True
- else:
- migration_script._upgrade_ops.append(
- ops.UpgradeOps([], upgrade_token=upgrade_token)
- )
- migration_script._downgrade_ops.append(
- ops.DowngradeOps([], downgrade_token=downgrade_token)
- )
-
- autogen_context = AutogenContext(
- migration_context, autogenerate=autogenerate
- )
- self._last_autogen_context: AutogenContext = autogen_context
-
- if autogenerate:
- compare._populate_migration_script(
- autogen_context, migration_script
- )
-
- if self.process_revision_directives:
- self.process_revision_directives(
- migration_context, rev, self.generated_revisions
- )
-
- hook = migration_context.opts["process_revision_directives"]
- if hook:
- hook(migration_context, rev, self.generated_revisions)
-
- for migration_script in self.generated_revisions:
- migration_script._needs_render = True
-
- def _default_revision(self) -> MigrationScript:
- command_args: Dict[str, Any] = self.command_args
- op = ops.MigrationScript(
- rev_id=command_args["rev_id"] or util.rev_id(),
- message=command_args["message"],
- upgrade_ops=ops.UpgradeOps([]),
- downgrade_ops=ops.DowngradeOps([]),
- head=command_args["head"],
- splice=command_args["splice"],
- branch_label=command_args["branch_label"],
- version_path=command_args["version_path"],
- depends_on=command_args["depends_on"],
- )
- return op
-
- def generate_scripts(self) -> Iterator[Optional[Script]]:
- for generated_revision in self.generated_revisions:
- yield self._to_script(generated_revision)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py
deleted file mode 100644
index a49640c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from __future__ import annotations
-
-import logging
-from typing import TYPE_CHECKING
-
-from . import comments
-from . import constraints
-from . import schema
-from . import server_defaults
-from . import tables
-from . import types
-from ... import util
-from ...runtime.plugins import Plugin
-
-if TYPE_CHECKING:
- from ..api import AutogenContext
- from ...operations.ops import MigrationScript
- from ...operations.ops import UpgradeOps
-
-
-log = logging.getLogger(__name__)
-
-comparators = util.PriorityDispatcher()
-"""global registry which alembic keeps empty, but copies when creating
-a new AutogenContext.
-
-This is to support a variety of third party plugins that hook their autogen
-functionality onto this collection.
-
-"""
-
-
-def _populate_migration_script(
- autogen_context: AutogenContext, migration_script: MigrationScript
-) -> None:
- upgrade_ops = migration_script.upgrade_ops_list[-1]
- downgrade_ops = migration_script.downgrade_ops_list[-1]
-
- _produce_net_changes(autogen_context, upgrade_ops)
- upgrade_ops.reverse_into(downgrade_ops)
-
-
-def _produce_net_changes(
- autogen_context: AutogenContext, upgrade_ops: UpgradeOps
-) -> None:
- assert autogen_context.dialect is not None
-
- autogen_context.comparators.dispatch(
- "autogenerate", qualifier=autogen_context.dialect.name
- )(autogen_context, upgrade_ops)
-
-
-Plugin.setup_plugin_from_module(schema, "alembic.autogenerate.schemas")
-Plugin.setup_plugin_from_module(tables, "alembic.autogenerate.tables")
-Plugin.setup_plugin_from_module(types, "alembic.autogenerate.types")
-Plugin.setup_plugin_from_module(
- constraints, "alembic.autogenerate.constraints"
-)
-Plugin.setup_plugin_from_module(
- server_defaults, "alembic.autogenerate.defaults"
-)
-Plugin.setup_plugin_from_module(comments, "alembic.autogenerate.comments")
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index a81b345..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc
deleted file mode 100644
index d446c80..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc
deleted file mode 100644
index 35e7e48..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc
deleted file mode 100644
index 9ec03bd..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc
deleted file mode 100644
index 49e86db..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc
deleted file mode 100644
index a192aac..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc
deleted file mode 100644
index 02f3b33..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc
deleted file mode 100644
index bbe28df..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py
deleted file mode 100644
index 70de74e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from __future__ import annotations
-
-import logging
-from typing import Any
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from ...operations import ops
-from ...util import PriorityDispatchResult
-
-if TYPE_CHECKING:
-
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import Table
-
- from ..api import AutogenContext
- from ...operations.ops import AlterColumnOp
- from ...operations.ops import ModifyTableOps
- from ...runtime.plugins import Plugin
-
-log = logging.getLogger(__name__)
-
-
-def _compare_column_comment(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: quoted_name,
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
- assert autogen_context.dialect is not None
- if not autogen_context.dialect.supports_comments:
- return PriorityDispatchResult.CONTINUE
-
- metadata_comment = metadata_col.comment
- conn_col_comment = conn_col.comment
- if conn_col_comment is None and metadata_comment is None:
- return PriorityDispatchResult.CONTINUE
-
- alter_column_op.existing_comment = conn_col_comment
-
- if conn_col_comment != metadata_comment:
- alter_column_op.modify_comment = metadata_comment
- log.info("Detected column comment '%s.%s'", tname, cname)
-
- return PriorityDispatchResult.STOP
- else:
- return PriorityDispatchResult.CONTINUE
-
-
-def _compare_table_comment(
- autogen_context: AutogenContext,
- modify_table_ops: ModifyTableOps,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- conn_table: Optional[Table],
- metadata_table: Optional[Table],
-) -> PriorityDispatchResult:
- assert autogen_context.dialect is not None
- if not autogen_context.dialect.supports_comments:
- return PriorityDispatchResult.CONTINUE
-
- # if we're doing CREATE TABLE, comments will be created inline
- # with the create_table op.
- if conn_table is None or metadata_table is None:
- return PriorityDispatchResult.CONTINUE
-
- if conn_table.comment is None and metadata_table.comment is None:
- return PriorityDispatchResult.CONTINUE
-
- if metadata_table.comment is None and conn_table.comment is not None:
- modify_table_ops.ops.append(
- ops.DropTableCommentOp(
- tname, existing_comment=conn_table.comment, schema=schema
- )
- )
- return PriorityDispatchResult.STOP
- elif metadata_table.comment != conn_table.comment:
- modify_table_ops.ops.append(
- ops.CreateTableCommentOp(
- tname,
- metadata_table.comment,
- existing_comment=conn_table.comment,
- schema=schema,
- )
- )
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def setup(plugin: Plugin) -> None:
- plugin.add_autogenerate_comparator(
- _compare_column_comment,
- "column",
- "comments",
- )
- plugin.add_autogenerate_comparator(
- _compare_table_comment,
- "table",
- "comments",
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py
deleted file mode 100644
index ae1f20e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py
+++ /dev/null
@@ -1,812 +0,0 @@
-# mypy: allow-untyped-defs, allow-untyped-calls, allow-incomplete-defs
-
-from __future__ import annotations
-
-import logging
-from typing import Any
-from typing import cast
-from typing import Collection
-from typing import Dict
-from typing import Mapping
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy import schema as sa_schema
-from sqlalchemy import text
-from sqlalchemy.sql import expression
-from sqlalchemy.sql.schema import ForeignKeyConstraint
-from sqlalchemy.sql.schema import Index
-from sqlalchemy.sql.schema import UniqueConstraint
-
-from .util import _InspectorConv
-from ... import util
-from ...ddl._autogen import is_index_sig
-from ...ddl._autogen import is_uq_sig
-from ...operations import ops
-from ...util import PriorityDispatchResult
-from ...util import sqla_compat
-
-if TYPE_CHECKING:
- from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint
- from sqlalchemy.engine.interfaces import ReflectedIndex
- from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.elements import TextClause
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import Table
-
- from ...autogenerate.api import AutogenContext
- from ...ddl._autogen import _constraint_sig
- from ...ddl.impl import DefaultImpl
- from ...operations.ops import AlterColumnOp
- from ...operations.ops import ModifyTableOps
- from ...runtime.plugins import Plugin
-
-_C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index])
-
-
-log = logging.getLogger(__name__)
-
-
-def _compare_indexes_and_uniques(
- autogen_context: AutogenContext,
- modify_ops: ModifyTableOps,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- conn_table: Optional[Table],
- metadata_table: Optional[Table],
-) -> PriorityDispatchResult:
- inspector = autogen_context.inspector
- is_create_table = conn_table is None
- is_drop_table = metadata_table is None
- impl = autogen_context.migration_context.impl
-
- # 1a. get raw indexes and unique constraints from metadata ...
- if metadata_table is not None:
- metadata_unique_constraints = {
- uq
- for uq in metadata_table.constraints
- if isinstance(uq, sa_schema.UniqueConstraint)
- }
- metadata_indexes = set(metadata_table.indexes)
- else:
- metadata_unique_constraints = set()
- metadata_indexes = set()
-
- conn_uniques: Collection[UniqueConstraint] = frozenset()
- conn_indexes: Collection[Index] = frozenset()
-
- supports_unique_constraints = False
-
- unique_constraints_duplicate_unique_indexes = False
-
- if conn_table is not None:
- conn_uniques_reflected: Collection[ReflectedUniqueConstraint] = (
- frozenset()
- )
- conn_indexes_reflected: Collection[ReflectedIndex] = frozenset()
-
- # 1b. ... and from connection, if the table exists
- try:
- conn_uniques_reflected = _InspectorConv(
- inspector
- ).get_unique_constraints(tname, schema=schema)
-
- supports_unique_constraints = True
- except NotImplementedError:
- pass
- except TypeError:
- # number of arguments is off for the base
- # method in SQLAlchemy due to the cache decorator
- # not being present
- pass
- else:
- conn_uniques_reflected = [
- uq
- for uq in conn_uniques_reflected
- if autogen_context.run_name_filters(
- uq["name"],
- "unique_constraint",
- {"table_name": tname, "schema_name": schema},
- )
- ]
- for uq in conn_uniques_reflected:
- if uq.get("duplicates_index"):
- unique_constraints_duplicate_unique_indexes = True
- try:
- conn_indexes_reflected = _InspectorConv(inspector).get_indexes(
- tname, schema=schema
- )
- except NotImplementedError:
- pass
- else:
- conn_indexes_reflected = [
- ix
- for ix in conn_indexes_reflected
- if autogen_context.run_name_filters(
- ix["name"],
- "index",
- {"table_name": tname, "schema_name": schema},
- )
- ]
-
- # 2. convert conn-level objects from raw inspector records
- # into schema objects
- if is_drop_table:
- # for DROP TABLE uniques are inline, don't need them
- conn_uniques = set()
- else:
- conn_uniques = {
- _make_unique_constraint(impl, uq_def, conn_table)
- for uq_def in conn_uniques_reflected
- }
-
- conn_indexes = {
- index
- for index in (
- _make_index(impl, ix, conn_table)
- for ix in conn_indexes_reflected
- )
- if index is not None
- }
-
- # 2a. if the dialect dupes unique indexes as unique constraints
- # (mysql and oracle), correct for that
-
- if unique_constraints_duplicate_unique_indexes:
- _correct_for_uq_duplicates_uix(
- conn_uniques,
- conn_indexes,
- metadata_unique_constraints,
- metadata_indexes,
- autogen_context.dialect,
- impl,
- )
-
- # 3. give the dialect a chance to omit indexes and constraints that
- # we know are either added implicitly by the DB or that the DB
- # can't accurately report on
- impl.correct_for_autogen_constraints(
- conn_uniques, # type: ignore[arg-type]
- conn_indexes, # type: ignore[arg-type]
- metadata_unique_constraints,
- metadata_indexes,
- )
-
- # 4. organize the constraints into "signature" collections, the
- # _constraint_sig() objects provide a consistent facade over both
- # Index and UniqueConstraint so we can easily work with them
- # interchangeably
- metadata_unique_constraints_sig = {
- impl._create_metadata_constraint_sig(uq)
- for uq in metadata_unique_constraints
- }
-
- metadata_indexes_sig = {
- impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes
- }
-
- conn_unique_constraints = {
- impl._create_reflected_constraint_sig(uq) for uq in conn_uniques
- }
-
- conn_indexes_sig = {
- impl._create_reflected_constraint_sig(ix) for ix in conn_indexes
- }
-
- # 5. index things by name, for those objects that have names
- metadata_names = {
- cast(str, c.md_name_to_sql_name(autogen_context)): c
- for c in metadata_unique_constraints_sig.union(metadata_indexes_sig)
- if c.is_named
- }
-
- conn_uniques_by_name: Dict[
- sqla_compat._ConstraintName,
- _constraint_sig[sa_schema.UniqueConstraint],
- ]
- conn_indexes_by_name: Dict[
- sqla_compat._ConstraintName, _constraint_sig[sa_schema.Index]
- ]
-
- conn_uniques_by_name = {c.name: c for c in conn_unique_constraints}
- conn_indexes_by_name = {c.name: c for c in conn_indexes_sig}
- conn_names = {
- c.name: c
- for c in conn_unique_constraints.union(conn_indexes_sig)
- if sqla_compat.constraint_name_string(c.name)
- }
-
- doubled_constraints = {
- name: (conn_uniques_by_name[name], conn_indexes_by_name[name])
- for name in set(conn_uniques_by_name).intersection(
- conn_indexes_by_name
- )
- }
-
- # 6. index things by "column signature", to help with unnamed unique
- # constraints.
- conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints}
- metadata_uniques_by_sig = {
- uq.unnamed: uq for uq in metadata_unique_constraints_sig
- }
- unnamed_metadata_uniques = {
- uq.unnamed: uq
- for uq in metadata_unique_constraints_sig
- if not sqla_compat._constraint_is_named(
- uq.const, autogen_context.dialect
- )
- }
-
- # assumptions:
- # 1. a unique constraint or an index from the connection *always*
- # has a name.
- # 2. an index on the metadata side *always* has a name.
- # 3. a unique constraint on the metadata side *might* have a name.
- # 4. The backend may double up indexes as unique constraints and
- # vice versa (e.g. MySQL, Postgresql)
-
- def obj_added(
- obj: (
- _constraint_sig[sa_schema.UniqueConstraint]
- | _constraint_sig[sa_schema.Index]
- ),
- ):
- if is_index_sig(obj):
- if autogen_context.run_object_filters(
- obj.const, obj.name, "index", False, None
- ):
- modify_ops.ops.append(ops.CreateIndexOp.from_index(obj.const))
- log.info(
- "Detected added index %r on '%s'",
- obj.name,
- obj.column_names,
- )
- elif is_uq_sig(obj):
- if not supports_unique_constraints:
- # can't report unique indexes as added if we don't
- # detect them
- return
- if is_create_table or is_drop_table:
- # unique constraints are created inline with table defs
- return
- if autogen_context.run_object_filters(
- obj.const, obj.name, "unique_constraint", False, None
- ):
- modify_ops.ops.append(
- ops.AddConstraintOp.from_constraint(obj.const)
- )
- log.info(
- "Detected added unique constraint %r on '%s'",
- obj.name,
- obj.column_names,
- )
- else:
- assert False
-
- def obj_removed(
- obj: (
- _constraint_sig[sa_schema.UniqueConstraint]
- | _constraint_sig[sa_schema.Index]
- ),
- ):
- if is_index_sig(obj):
- if obj.is_unique and not supports_unique_constraints:
- # many databases double up unique constraints
- # as unique indexes. without that list we can't
- # be sure what we're doing here
- return
-
- if autogen_context.run_object_filters(
- obj.const, obj.name, "index", True, None
- ):
- modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const))
- log.info("Detected removed index %r on %r", obj.name, tname)
- elif is_uq_sig(obj):
- if is_create_table or is_drop_table:
- # if the whole table is being dropped, we don't need to
- # consider unique constraint separately
- return
- if autogen_context.run_object_filters(
- obj.const, obj.name, "unique_constraint", True, None
- ):
- modify_ops.ops.append(
- ops.DropConstraintOp.from_constraint(obj.const)
- )
- log.info(
- "Detected removed unique constraint %r on %r",
- obj.name,
- tname,
- )
- else:
- assert False
-
- def obj_changed(
- old: (
- _constraint_sig[sa_schema.UniqueConstraint]
- | _constraint_sig[sa_schema.Index]
- ),
- new: (
- _constraint_sig[sa_schema.UniqueConstraint]
- | _constraint_sig[sa_schema.Index]
- ),
- msg: str,
- ):
- if is_index_sig(old):
- assert is_index_sig(new)
-
- if autogen_context.run_object_filters(
- new.const, new.name, "index", False, old.const
- ):
- log.info(
- "Detected changed index %r on %r: %s", old.name, tname, msg
- )
- modify_ops.ops.append(ops.DropIndexOp.from_index(old.const))
- modify_ops.ops.append(ops.CreateIndexOp.from_index(new.const))
- elif is_uq_sig(old):
- assert is_uq_sig(new)
-
- if autogen_context.run_object_filters(
- new.const, new.name, "unique_constraint", False, old.const
- ):
- log.info(
- "Detected changed unique constraint %r on %r: %s",
- old.name,
- tname,
- msg,
- )
- modify_ops.ops.append(
- ops.DropConstraintOp.from_constraint(old.const)
- )
- modify_ops.ops.append(
- ops.AddConstraintOp.from_constraint(new.const)
- )
- else:
- assert False
-
- for removed_name in sorted(set(conn_names).difference(metadata_names)):
- conn_obj = conn_names[removed_name]
- if (
- is_uq_sig(conn_obj)
- and conn_obj.unnamed in unnamed_metadata_uniques
- ):
- continue
- elif removed_name in doubled_constraints:
- conn_uq, conn_idx = doubled_constraints[removed_name]
- if (
- all(
- conn_idx.unnamed != meta_idx.unnamed
- for meta_idx in metadata_indexes_sig
- )
- and conn_uq.unnamed not in metadata_uniques_by_sig
- ):
- obj_removed(conn_uq)
- obj_removed(conn_idx)
- else:
- obj_removed(conn_obj)
-
- for existing_name in sorted(set(metadata_names).intersection(conn_names)):
- metadata_obj = metadata_names[existing_name]
-
- if existing_name in doubled_constraints:
- conn_uq, conn_idx = doubled_constraints[existing_name]
- if is_index_sig(metadata_obj):
- conn_obj = conn_idx
- else:
- conn_obj = conn_uq
- else:
- conn_obj = conn_names[existing_name]
-
- if type(conn_obj) != type(metadata_obj):
- obj_removed(conn_obj)
- obj_added(metadata_obj)
- else:
- # TODO: for plugins, let's do is_index_sig / is_uq_sig
- # here so we know index or unique, then
- # do a sub-dispatch,
- # autogen_context.comparators.dispatch("index")
- # or
- # autogen_context.comparators.dispatch("unique_constraint")
- #
- comparison = metadata_obj.compare_to_reflected(conn_obj)
-
- if comparison.is_different:
- # constraint are different
- obj_changed(conn_obj, metadata_obj, comparison.message)
- elif comparison.is_skip:
- # constraint cannot be compared, skip them
- thing = (
- "index" if is_index_sig(conn_obj) else "unique constraint"
- )
- log.info(
- "Cannot compare %s %r, assuming equal and skipping. %s",
- thing,
- conn_obj.name,
- comparison.message,
- )
- else:
- # constraint are equal
- assert comparison.is_equal
-
- for added_name in sorted(set(metadata_names).difference(conn_names)):
- obj = metadata_names[added_name]
- obj_added(obj)
-
- for uq_sig in unnamed_metadata_uniques:
- if uq_sig not in conn_uniques_by_sig:
- obj_added(unnamed_metadata_uniques[uq_sig])
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _correct_for_uq_duplicates_uix(
- conn_unique_constraints,
- conn_indexes,
- metadata_unique_constraints,
- metadata_indexes,
- dialect,
- impl,
-):
- # dedupe unique indexes vs. constraints, since MySQL / Oracle
- # doesn't really have unique constraints as a separate construct.
- # but look in the metadata and try to maintain constructs
- # that already seem to be defined one way or the other
- # on that side. This logic was formerly local to MySQL dialect,
- # generalized to Oracle and others. See #276
-
- # resolve final rendered name for unique constraints defined in the
- # metadata. this includes truncation of long names. naming convention
- # names currently should already be set as cons.name, however leave this
- # to the sqla_compat to decide.
- metadata_cons_names = [
- (sqla_compat._get_constraint_final_name(cons, dialect), cons)
- for cons in metadata_unique_constraints
- ]
-
- metadata_uq_names = {
- name for name, cons in metadata_cons_names if name is not None
- }
-
- unnamed_metadata_uqs = {
- impl._create_metadata_constraint_sig(cons).unnamed
- for name, cons in metadata_cons_names
- if name is None
- }
-
- metadata_ix_names = {
- sqla_compat._get_constraint_final_name(cons, dialect)
- for cons in metadata_indexes
- if cons.unique
- }
-
- # for reflection side, names are in their final database form
- # already since they're from the database
- conn_ix_names = {cons.name: cons for cons in conn_indexes if cons.unique}
-
- uqs_dupe_indexes = {
- cons.name: cons
- for cons in conn_unique_constraints
- if cons.info["duplicates_index"]
- }
-
- for overlap in uqs_dupe_indexes:
- if overlap not in metadata_uq_names:
- if (
- impl._create_reflected_constraint_sig(
- uqs_dupe_indexes[overlap]
- ).unnamed
- not in unnamed_metadata_uqs
- ):
- conn_unique_constraints.discard(uqs_dupe_indexes[overlap])
- elif overlap not in metadata_ix_names:
- conn_indexes.discard(conn_ix_names[overlap])
-
-
-_IndexColumnSortingOps: Mapping[str, Any] = util.immutabledict(
- {
- "asc": expression.asc,
- "desc": expression.desc,
- "nulls_first": expression.nullsfirst,
- "nulls_last": expression.nullslast,
- "nullsfirst": expression.nullsfirst, # 1_3 name
- "nullslast": expression.nullslast, # 1_3 name
- }
-)
-
-
-def _make_index(
- impl: DefaultImpl, params: ReflectedIndex, conn_table: Table
-) -> Optional[Index]:
- exprs: list[Union[Column[Any], TextClause]] = []
- sorting = params.get("column_sorting")
-
- for num, col_name in enumerate(params["column_names"]):
- item: Union[Column[Any], TextClause]
- if col_name is None:
- assert "expressions" in params
- name = params["expressions"][num]
- item = text(name)
- else:
- name = col_name
- item = conn_table.c[col_name]
- if sorting and name in sorting:
- for operator in sorting[name]:
- if operator in _IndexColumnSortingOps:
- item = _IndexColumnSortingOps[operator](item)
- exprs.append(item)
- ix = sa_schema.Index(
- params["name"],
- *exprs,
- unique=params["unique"],
- _table=conn_table,
- **impl.adjust_reflected_dialect_options(params, "index"),
- )
- if "duplicates_constraint" in params:
- ix.info["duplicates_constraint"] = params["duplicates_constraint"]
- return ix
-
-
-def _make_unique_constraint(
- impl: DefaultImpl, params: ReflectedUniqueConstraint, conn_table: Table
-) -> UniqueConstraint:
- uq = sa_schema.UniqueConstraint(
- *[conn_table.c[cname] for cname in params["column_names"]],
- name=params["name"],
- **impl.adjust_reflected_dialect_options(params, "unique_constraint"),
- )
- if "duplicates_index" in params:
- uq.info["duplicates_index"] = params["duplicates_index"]
-
- return uq
-
-
-def _make_foreign_key(
- params: ReflectedForeignKeyConstraint, conn_table: Table
-) -> ForeignKeyConstraint:
- tname = params["referred_table"]
- if params["referred_schema"]:
- tname = "%s.%s" % (params["referred_schema"], tname)
-
- options = params.get("options", {})
-
- const = sa_schema.ForeignKeyConstraint(
- [conn_table.c[cname] for cname in params["constrained_columns"]],
- ["%s.%s" % (tname, n) for n in params["referred_columns"]],
- onupdate=options.get("onupdate"),
- ondelete=options.get("ondelete"),
- deferrable=options.get("deferrable"),
- initially=options.get("initially"),
- name=params["name"],
- )
-
- referred_schema = params["referred_schema"]
- referred_table = params["referred_table"]
-
- remote_table_key = sqla_compat._get_table_key(
- referred_table, referred_schema
- )
- if remote_table_key not in conn_table.metadata:
- # create a placeholder table
- sa_schema.Table(
- referred_table,
- conn_table.metadata,
- schema=(
- referred_schema
- if referred_schema is not None
- else sa_schema.BLANK_SCHEMA
- ),
- *[
- sa_schema.Column(remote, conn_table.c[local].type)
- for local, remote in zip(
- params["constrained_columns"], params["referred_columns"]
- )
- ],
- info={"alembic_placeholder": True},
- )
- elif conn_table.metadata.tables[remote_table_key].info.get(
- "alembic_placeholder"
- ):
- # table exists and is a placeholder; ensure needed columns are present
- placeholder_table = conn_table.metadata.tables[remote_table_key]
- for local, remote in zip(
- params["constrained_columns"], params["referred_columns"]
- ):
- if remote not in placeholder_table.c:
- placeholder_table.append_column(
- sa_schema.Column(remote, conn_table.c[local].type)
- )
-
- # needed by 0.7
- conn_table.append_constraint(const)
- return const
-
-
-def _compare_foreign_keys(
- autogen_context: AutogenContext,
- modify_table_ops: ModifyTableOps,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- conn_table: Table,
- metadata_table: Table,
-) -> PriorityDispatchResult:
- # if we're doing CREATE TABLE, all FKs are created
- # inline within the table def
-
- if conn_table is None or metadata_table is None:
- return PriorityDispatchResult.CONTINUE
-
- inspector = autogen_context.inspector
- metadata_fks = {
- fk
- for fk in metadata_table.constraints
- if isinstance(fk, sa_schema.ForeignKeyConstraint)
- }
-
- conn_fks_list = [
- fk
- for fk in _InspectorConv(inspector).get_foreign_keys(
- tname, schema=schema
- )
- if autogen_context.run_name_filters(
- fk["name"],
- "foreign_key_constraint",
- {"table_name": tname, "schema_name": schema},
- )
- ]
-
- conn_fks = {
- _make_foreign_key(const, conn_table) for const in conn_fks_list
- }
-
- impl = autogen_context.migration_context.impl
-
- # give the dialect a chance to correct the FKs to match more
- # closely
- autogen_context.migration_context.impl.correct_for_autogen_foreignkeys(
- conn_fks, metadata_fks
- )
-
- metadata_fks_sig = {
- impl._create_metadata_constraint_sig(fk) for fk in metadata_fks
- }
-
- conn_fks_sig = {
- impl._create_reflected_constraint_sig(fk) for fk in conn_fks
- }
-
- # check if reflected FKs include options, indicating the backend
- # can reflect FK options
- if conn_fks_list and "options" in conn_fks_list[0]:
- conn_fks_by_sig = {c.unnamed: c for c in conn_fks_sig}
- metadata_fks_by_sig = {c.unnamed: c for c in metadata_fks_sig}
- else:
- # otherwise compare by sig without options added
- conn_fks_by_sig = {c.unnamed_no_options: c for c in conn_fks_sig}
- metadata_fks_by_sig = {
- c.unnamed_no_options: c for c in metadata_fks_sig
- }
-
- metadata_fks_by_name = {
- c.name: c for c in metadata_fks_sig if c.name is not None
- }
- conn_fks_by_name = {c.name: c for c in conn_fks_sig if c.name is not None}
-
- def _add_fk(obj, compare_to):
- if autogen_context.run_object_filters(
- obj.const, obj.name, "foreign_key_constraint", False, compare_to
- ):
- modify_table_ops.ops.append(
- ops.CreateForeignKeyOp.from_constraint(const.const)
- )
-
- log.info(
- "Detected added foreign key (%s)(%s) on table %s%s",
- ", ".join(obj.source_columns),
- ", ".join(obj.target_columns),
- "%s." % obj.source_schema if obj.source_schema else "",
- obj.source_table,
- )
-
- def _remove_fk(obj, compare_to):
- if autogen_context.run_object_filters(
- obj.const, obj.name, "foreign_key_constraint", True, compare_to
- ):
- modify_table_ops.ops.append(
- ops.DropConstraintOp.from_constraint(obj.const)
- )
- log.info(
- "Detected removed foreign key (%s)(%s) on table %s%s",
- ", ".join(obj.source_columns),
- ", ".join(obj.target_columns),
- "%s." % obj.source_schema if obj.source_schema else "",
- obj.source_table,
- )
-
- # so far it appears we don't need to do this by name at all.
- # SQLite doesn't preserve constraint names anyway
-
- for removed_sig in set(conn_fks_by_sig).difference(metadata_fks_by_sig):
- const = conn_fks_by_sig[removed_sig]
- if removed_sig not in metadata_fks_by_sig:
- compare_to = (
- metadata_fks_by_name[const.name].const
- if const.name and const.name in metadata_fks_by_name
- else None
- )
- _remove_fk(const, compare_to)
-
- for added_sig in set(metadata_fks_by_sig).difference(conn_fks_by_sig):
- const = metadata_fks_by_sig[added_sig]
- if added_sig not in conn_fks_by_sig:
- compare_to = (
- conn_fks_by_name[const.name].const
- if const.name and const.name in conn_fks_by_name
- else None
- )
- _add_fk(const, compare_to)
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _compare_nullable(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
- metadata_col_nullable = metadata_col.nullable
- conn_col_nullable = conn_col.nullable
- alter_column_op.existing_nullable = conn_col_nullable
-
- if conn_col_nullable is not metadata_col_nullable:
- if (
- sqla_compat._server_default_is_computed(
- metadata_col.server_default, conn_col.server_default
- )
- and sqla_compat._nullability_might_be_unset(metadata_col)
- or (
- sqla_compat._server_default_is_identity(
- metadata_col.server_default, conn_col.server_default
- )
- )
- ):
- log.info(
- "Ignoring nullable change on identity column '%s.%s'",
- tname,
- cname,
- )
- else:
- alter_column_op.modify_nullable = metadata_col_nullable
- log.info(
- "Detected %s on column '%s.%s'",
- "NULL" if metadata_col_nullable else "NOT NULL",
- tname,
- cname,
- )
- # column nullablity changed, no further nullable checks needed
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def setup(plugin: Plugin) -> None:
- plugin.add_autogenerate_comparator(
- _compare_indexes_and_uniques,
- "table",
- "indexes",
- )
- plugin.add_autogenerate_comparator(
- _compare_foreign_keys,
- "table",
- "foreignkeys",
- )
- plugin.add_autogenerate_comparator(
- _compare_nullable,
- "column",
- "nullable",
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py
deleted file mode 100644
index 1f46aff..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# mypy: allow-untyped-calls
-
-from __future__ import annotations
-
-import logging
-from typing import Optional
-from typing import Set
-from typing import TYPE_CHECKING
-
-from sqlalchemy import inspect
-
-from ...util import PriorityDispatchResult
-
-if TYPE_CHECKING:
- from sqlalchemy.engine.reflection import Inspector
-
- from ...autogenerate.api import AutogenContext
- from ...operations.ops import UpgradeOps
- from ...runtime.plugins import Plugin
-
-
-log = logging.getLogger(__name__)
-
-
-def _produce_net_changes(
- autogen_context: AutogenContext, upgrade_ops: UpgradeOps
-) -> PriorityDispatchResult:
- connection = autogen_context.connection
- assert connection is not None
- include_schemas = autogen_context.opts.get("include_schemas", False)
-
- inspector: Inspector = inspect(connection)
-
- default_schema = connection.dialect.default_schema_name
- schemas: Set[Optional[str]]
- if include_schemas:
- schemas = set(inspector.get_schema_names())
- # replace default schema name with None
- schemas.discard("information_schema")
- # replace the "default" schema with None
- schemas.discard(default_schema)
- schemas.add(None)
- else:
- schemas = {None}
-
- schemas = {
- s for s in schemas if autogen_context.run_name_filters(s, "schema", {})
- }
-
- assert autogen_context.dialect is not None
- autogen_context.comparators.dispatch(
- "schema", qualifier=autogen_context.dialect.name
- )(autogen_context, upgrade_ops, schemas)
-
- return PriorityDispatchResult.CONTINUE
-
-
-def setup(plugin: Plugin) -> None:
- plugin.add_autogenerate_comparator(
- _produce_net_changes,
- "autogenerate",
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py
deleted file mode 100644
index 1e09e8e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py
+++ /dev/null
@@ -1,344 +0,0 @@
-from __future__ import annotations
-
-import logging
-import re
-from types import NoneType
-from typing import Any
-from typing import cast
-from typing import Optional
-from typing import Sequence
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import schema as sa_schema
-from sqlalchemy.sql.schema import DefaultClause
-
-from ... import util
-from ...util import DispatchPriority
-from ...util import PriorityDispatchResult
-from ...util import sqla_compat
-
-if TYPE_CHECKING:
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Column
-
- from ...autogenerate.api import AutogenContext
- from ...operations.ops import AlterColumnOp
- from ...runtime.plugins import Plugin
-
-log = logging.getLogger(__name__)
-
-
-def _render_server_default_for_compare(
- metadata_default: Optional[Any], autogen_context: AutogenContext
-) -> Optional[str]:
- if isinstance(metadata_default, sa_schema.DefaultClause):
- if isinstance(metadata_default.arg, str):
- metadata_default = metadata_default.arg
- else:
- metadata_default = str(
- metadata_default.arg.compile(
- dialect=autogen_context.dialect,
- compile_kwargs={"literal_binds": True},
- )
- )
- if isinstance(metadata_default, str):
- return metadata_default
- else:
- return None
-
-
-def _normalize_computed_default(sqltext: str) -> str:
- """we want to warn if a computed sql expression has changed. however
- we don't want false positives and the warning is not that critical.
- so filter out most forms of variability from the SQL text.
-
- """
-
- return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower()
-
-
-def _compare_computed_default(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: str,
- cname: str,
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
-
- metadata_default = metadata_col.server_default
- conn_col_default = conn_col.server_default
- if conn_col_default is None and metadata_default is None:
- return PriorityDispatchResult.CONTINUE
-
- if sqla_compat._server_default_is_computed(
- conn_col_default
- ) and not sqla_compat._server_default_is_computed(metadata_default):
- _warn_computed_not_supported(tname, cname)
- return PriorityDispatchResult.STOP
-
- if not sqla_compat._server_default_is_computed(metadata_default):
- return PriorityDispatchResult.CONTINUE
-
- rendered_metadata_default = str(
- cast(sa_schema.Computed, metadata_col.server_default).sqltext.compile(
- dialect=autogen_context.dialect,
- compile_kwargs={"literal_binds": True},
- )
- )
-
- # since we cannot change computed columns, we do only a crude comparison
- # here where we try to eliminate syntactical differences in order to
- # get a minimal comparison just to emit a warning.
-
- rendered_metadata_default = _normalize_computed_default(
- rendered_metadata_default
- )
-
- if isinstance(conn_col.server_default, sa_schema.Computed):
- rendered_conn_default = str(
- conn_col.server_default.sqltext.compile(
- dialect=autogen_context.dialect,
- compile_kwargs={"literal_binds": True},
- )
- )
- rendered_conn_default = _normalize_computed_default(
- rendered_conn_default
- )
- else:
- rendered_conn_default = ""
-
- if rendered_metadata_default != rendered_conn_default:
- _warn_computed_not_supported(tname, cname)
-
- return PriorityDispatchResult.STOP
-
-
-def _warn_computed_not_supported(tname: str, cname: str) -> None:
- util.warn("Computed default on %s.%s cannot be modified" % (tname, cname))
-
-
-def _compare_identity_default(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
- skip: Sequence[str] = (
- "order",
- "on_null",
- "oracle_order",
- "oracle_on_null",
- ),
-) -> PriorityDispatchResult:
-
- metadata_default = metadata_col.server_default
- conn_col_default = conn_col.server_default
- if (
- conn_col_default is None
- and metadata_default is None
- or not sqla_compat._server_default_is_identity(
- metadata_default, conn_col_default
- )
- ):
- return PriorityDispatchResult.CONTINUE
-
- assert isinstance(
- metadata_col.server_default,
- (sa_schema.Identity, sa_schema.Sequence, NoneType),
- )
- assert isinstance(
- conn_col.server_default,
- (sa_schema.Identity, sa_schema.Sequence, NoneType),
- )
-
- impl = autogen_context.migration_context.impl
- diff, _, is_alter = impl._compare_identity_default( # type: ignore[no-untyped-call] # noqa: E501
- metadata_col.server_default, conn_col.server_default
- )
-
- if is_alter:
- alter_column_op.modify_server_default = metadata_default
- if diff:
- log.info(
- "Detected server default on column '%s.%s': "
- "identity options attributes %s",
- tname,
- cname,
- sorted(diff),
- )
-
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _user_compare_server_default(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
-
- metadata_default = metadata_col.server_default
- conn_col_default = conn_col.server_default
- if conn_col_default is None and metadata_default is None:
- return PriorityDispatchResult.CONTINUE
-
- alter_column_op.existing_server_default = conn_col_default
-
- migration_context = autogen_context.migration_context
-
- if migration_context._user_compare_server_default is False:
- return PriorityDispatchResult.STOP
-
- if not callable(migration_context._user_compare_server_default):
- return PriorityDispatchResult.CONTINUE
-
- rendered_metadata_default = _render_server_default_for_compare(
- metadata_default, autogen_context
- )
- rendered_conn_default = (
- cast(Any, conn_col_default).arg.text if conn_col_default else None
- )
-
- is_diff = migration_context._user_compare_server_default(
- migration_context,
- conn_col,
- metadata_col,
- rendered_conn_default,
- metadata_col.server_default,
- rendered_metadata_default,
- )
- if is_diff:
- alter_column_op.modify_server_default = metadata_default
- log.info(
- "User defined function %s detected "
- "server default on column '%s.%s'",
- migration_context._user_compare_server_default,
- tname,
- cname,
- )
- return PriorityDispatchResult.STOP
- elif is_diff is False:
- # if user compare server_default returns False and not None,
- # it means "dont do any more server_default comparison"
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _dialect_impl_compare_server_default(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
- """use dialect.impl.compare_server_default.
-
- This would in theory not be needed. however we dont know if any
- third party libraries haven't made their own alembic dialect and
- implemented this method.
-
- """
- metadata_default = metadata_col.server_default
- conn_col_default = conn_col.server_default
- if conn_col_default is None and metadata_default is None:
- return PriorityDispatchResult.CONTINUE
-
- # this is already done by _user_compare_server_default,
- # but doing it here also for unit tests that want to call
- # _dialect_impl_compare_server_default directly
- alter_column_op.existing_server_default = conn_col_default
-
- if not isinstance(
- metadata_default, (DefaultClause, NoneType)
- ) or not isinstance(conn_col_default, (DefaultClause, NoneType)):
- return PriorityDispatchResult.CONTINUE
-
- migration_context = autogen_context.migration_context
-
- rendered_metadata_default = _render_server_default_for_compare(
- metadata_default, autogen_context
- )
- rendered_conn_default = (
- cast(Any, conn_col_default).arg.text if conn_col_default else None
- )
-
- is_diff = migration_context.impl.compare_server_default( # type: ignore[no-untyped-call] # noqa: E501
- conn_col,
- metadata_col,
- rendered_metadata_default,
- rendered_conn_default,
- )
- if is_diff:
- alter_column_op.modify_server_default = metadata_default
- log.info(
- "Dialect impl %s detected server default on column '%s.%s'",
- migration_context.impl,
- tname,
- cname,
- )
- return PriorityDispatchResult.STOP
- return PriorityDispatchResult.CONTINUE
-
-
-def _setup_autoincrement(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: quoted_name,
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
- if metadata_col.table._autoincrement_column is metadata_col:
- alter_column_op.kw["autoincrement"] = True
- elif metadata_col.autoincrement is True:
- alter_column_op.kw["autoincrement"] = True
- elif metadata_col.autoincrement is False:
- alter_column_op.kw["autoincrement"] = False
-
- return PriorityDispatchResult.CONTINUE
-
-
-def setup(plugin: Plugin) -> None:
- plugin.add_autogenerate_comparator(
- _user_compare_server_default,
- "column",
- "server_default",
- priority=DispatchPriority.FIRST,
- )
- plugin.add_autogenerate_comparator(
- _compare_computed_default,
- "column",
- "server_default",
- )
-
- plugin.add_autogenerate_comparator(
- _compare_identity_default,
- "column",
- "server_default",
- )
-
- plugin.add_autogenerate_comparator(
- _setup_autoincrement,
- "column",
- "server_default",
- )
- plugin.add_autogenerate_comparator(
- _dialect_impl_compare_server_default,
- "column",
- "server_default",
- priority=DispatchPriority.LAST,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py
deleted file mode 100644
index 31eddc6..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# mypy: allow-untyped-calls
-
-from __future__ import annotations
-
-import contextlib
-import logging
-from typing import Iterator
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import event
-from sqlalchemy import schema as sa_schema
-from sqlalchemy.util import OrderedSet
-
-from .util import _InspectorConv
-from ...operations import ops
-from ...util import PriorityDispatchResult
-
-if TYPE_CHECKING:
- from sqlalchemy.engine.reflection import Inspector
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Table
-
- from ...autogenerate.api import AutogenContext
- from ...operations.ops import ModifyTableOps
- from ...operations.ops import UpgradeOps
- from ...runtime.plugins import Plugin
-
-
-log = logging.getLogger(__name__)
-
-
-def _autogen_for_tables(
- autogen_context: AutogenContext,
- upgrade_ops: UpgradeOps,
- schemas: Set[Optional[str]],
-) -> PriorityDispatchResult:
- inspector = autogen_context.inspector
-
- conn_table_names: Set[Tuple[Optional[str], str]] = set()
-
- version_table_schema = (
- autogen_context.migration_context.version_table_schema
- )
- version_table = autogen_context.migration_context.version_table
-
- for schema_name in schemas:
- tables = available = set(inspector.get_table_names(schema=schema_name))
- if schema_name == version_table_schema:
- tables = tables.difference(
- [autogen_context.migration_context.version_table]
- )
-
- tablenames = [
- tname
- for tname in tables
- if autogen_context.run_name_filters(
- tname, "table", {"schema_name": schema_name}
- )
- ]
-
- conn_table_names.update((schema_name, tname) for tname in tablenames)
-
- inspector = autogen_context.inspector
- insp = _InspectorConv(inspector)
- insp.pre_cache_tables(schema_name, tablenames, available)
-
- metadata_table_names = OrderedSet(
- [(table.schema, table.name) for table in autogen_context.sorted_tables]
- ).difference([(version_table_schema, version_table)])
-
- _compare_tables(
- conn_table_names,
- metadata_table_names,
- inspector,
- upgrade_ops,
- autogen_context,
- )
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _compare_tables(
- conn_table_names: set[tuple[str | None, str]],
- metadata_table_names: set[tuple[str | None, str]],
- inspector: Inspector,
- upgrade_ops: UpgradeOps,
- autogen_context: AutogenContext,
-) -> None:
- default_schema = inspector.bind.dialect.default_schema_name
-
- # tables coming from the connection will not have "schema"
- # set if it matches default_schema_name; so we need a list
- # of table names from local metadata that also have "None" if schema
- # == default_schema_name. Most setups will be like this anyway but
- # some are not (see #170)
- metadata_table_names_no_dflt_schema = OrderedSet(
- [
- (schema if schema != default_schema else None, tname)
- for schema, tname in metadata_table_names
- ]
- )
-
- # to adjust for the MetaData collection storing the tables either
- # as "schemaname.tablename" or just "tablename", create a new lookup
- # which will match the "non-default-schema" keys to the Table object.
- tname_to_table = {
- no_dflt_schema: autogen_context.table_key_to_table[
- sa_schema._get_table_key(tname, schema)
- ]
- for no_dflt_schema, (schema, tname) in zip(
- metadata_table_names_no_dflt_schema, metadata_table_names
- )
- }
- metadata_table_names = metadata_table_names_no_dflt_schema
-
- for s, tname in metadata_table_names.difference(conn_table_names):
- name = "%s.%s" % (s, tname) if s else tname
- metadata_table = tname_to_table[(s, tname)]
- if autogen_context.run_object_filters(
- metadata_table, tname, "table", False, None
- ):
- upgrade_ops.ops.append(
- ops.CreateTableOp.from_table(metadata_table)
- )
- log.info("Detected added table %r", name)
- modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
-
- autogen_context.comparators.dispatch(
- "table", qualifier=autogen_context.dialect.name
- )(
- autogen_context,
- modify_table_ops,
- s,
- tname,
- None,
- metadata_table,
- )
- if not modify_table_ops.is_empty():
- upgrade_ops.ops.append(modify_table_ops)
-
- removal_metadata = sa_schema.MetaData()
- for s, tname in conn_table_names.difference(metadata_table_names):
- name = sa_schema._get_table_key(tname, s)
-
- # a name might be present already if a previous reflection pulled
- # this table in via foreign key constraint
- exists = name in removal_metadata.tables
- t = sa_schema.Table(tname, removal_metadata, schema=s)
-
- if not exists:
- event.listen(
- t,
- "column_reflect",
- # fmt: off
- autogen_context.migration_context.impl.
- _compat_autogen_column_reflect
- (inspector),
- # fmt: on
- )
- _InspectorConv(inspector).reflect_table(t)
- if autogen_context.run_object_filters(t, tname, "table", True, None):
- modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
-
- autogen_context.comparators.dispatch(
- "table", qualifier=autogen_context.dialect.name
- )(autogen_context, modify_table_ops, s, tname, t, None)
- if not modify_table_ops.is_empty():
- upgrade_ops.ops.append(modify_table_ops)
-
- upgrade_ops.ops.append(ops.DropTableOp.from_table(t))
- log.info("Detected removed table %r", name)
-
- existing_tables = conn_table_names.intersection(metadata_table_names)
-
- existing_metadata = sa_schema.MetaData()
- conn_column_info = {}
- for s, tname in existing_tables:
- name = sa_schema._get_table_key(tname, s)
- exists = name in existing_metadata.tables
-
- # a name might be present already if a previous reflection pulled
- # this table in via foreign key constraint
- t = sa_schema.Table(tname, existing_metadata, schema=s)
- if not exists:
- event.listen(
- t,
- "column_reflect",
- # fmt: off
- autogen_context.migration_context.impl.
- _compat_autogen_column_reflect(inspector),
- # fmt: on
- )
- _InspectorConv(inspector).reflect_table(t)
-
- conn_column_info[(s, tname)] = t
-
- for s, tname in sorted(existing_tables, key=lambda x: (x[0] or "", x[1])):
- s = s or None
- name = "%s.%s" % (s, tname) if s else tname
- metadata_table = tname_to_table[(s, tname)]
- conn_table = existing_metadata.tables[name]
-
- if autogen_context.run_object_filters(
- metadata_table, tname, "table", False, conn_table
- ):
- modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
- with _compare_columns(
- s,
- tname,
- conn_table,
- metadata_table,
- modify_table_ops,
- autogen_context,
- inspector,
- ):
- autogen_context.comparators.dispatch(
- "table", qualifier=autogen_context.dialect.name
- )(
- autogen_context,
- modify_table_ops,
- s,
- tname,
- conn_table,
- metadata_table,
- )
-
- if not modify_table_ops.is_empty():
- upgrade_ops.ops.append(modify_table_ops)
-
-
-@contextlib.contextmanager
-def _compare_columns(
- schema: Optional[str],
- tname: Union[quoted_name, str],
- conn_table: Table,
- metadata_table: Table,
- modify_table_ops: ModifyTableOps,
- autogen_context: AutogenContext,
- inspector: Inspector,
-) -> Iterator[None]:
- name = "%s.%s" % (schema, tname) if schema else tname
- metadata_col_names = OrderedSet(
- c.name for c in metadata_table.c if not c.system
- )
- metadata_cols_by_name = {
- c.name: c for c in metadata_table.c if not c.system
- }
-
- conn_col_names = {
- c.name: c
- for c in conn_table.c
- if autogen_context.run_name_filters(
- c.name, "column", {"table_name": tname, "schema_name": schema}
- )
- }
-
- for cname in metadata_col_names.difference(conn_col_names):
- if autogen_context.run_object_filters(
- metadata_cols_by_name[cname], cname, "column", False, None
- ):
- modify_table_ops.ops.append(
- ops.AddColumnOp.from_column_and_tablename(
- schema, tname, metadata_cols_by_name[cname]
- )
- )
- log.info("Detected added column '%s.%s'", name, cname)
-
- for colname in metadata_col_names.intersection(conn_col_names):
- metadata_col = metadata_cols_by_name[colname]
- conn_col = conn_table.c[colname]
- if not autogen_context.run_object_filters(
- metadata_col, colname, "column", False, conn_col
- ):
- continue
- alter_column_op = ops.AlterColumnOp(tname, colname, schema=schema)
-
- autogen_context.comparators.dispatch(
- "column", qualifier=autogen_context.dialect.name
- )(
- autogen_context,
- alter_column_op,
- schema,
- tname,
- colname,
- conn_col,
- metadata_col,
- )
-
- if alter_column_op.has_changes():
- modify_table_ops.ops.append(alter_column_op)
-
- yield
-
- for cname in set(conn_col_names).difference(metadata_col_names):
- if autogen_context.run_object_filters(
- conn_table.c[cname], cname, "column", True, None
- ):
- modify_table_ops.ops.append(
- ops.DropColumnOp.from_column_and_tablename(
- schema, tname, conn_table.c[cname]
- )
- )
- log.info("Detected removed column '%s.%s'", name, cname)
-
-
-def setup(plugin: Plugin) -> None:
-
- plugin.add_autogenerate_comparator(
- _autogen_for_tables,
- "schema",
- "tables",
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py
deleted file mode 100644
index 1d5d160..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py
+++ /dev/null
@@ -1,147 +0,0 @@
-from __future__ import annotations
-
-import logging
-from typing import Any
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import types as sqltypes
-
-from ...util import DispatchPriority
-from ...util import PriorityDispatchResult
-
-if TYPE_CHECKING:
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Column
-
- from ...autogenerate.api import AutogenContext
- from ...operations.ops import AlterColumnOp
- from ...runtime.plugins import Plugin
-
-
-log = logging.getLogger(__name__)
-
-
-def _compare_type_setup(
- alter_column_op: AlterColumnOp,
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> bool:
-
- conn_type = conn_col.type
- alter_column_op.existing_type = conn_type
- metadata_type = metadata_col.type
- if conn_type._type_affinity is sqltypes.NullType:
- log.info(
- "Couldn't determine database type for column '%s.%s'",
- tname,
- cname,
- )
- return False
- if metadata_type._type_affinity is sqltypes.NullType:
- log.info(
- "Column '%s.%s' has no type within the model; can't compare",
- tname,
- cname,
- )
- return False
-
- return True
-
-
-def _user_compare_type(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
-
- migration_context = autogen_context.migration_context
-
- if migration_context._user_compare_type is False:
- return PriorityDispatchResult.STOP
-
- if not _compare_type_setup(
- alter_column_op, tname, cname, conn_col, metadata_col
- ):
- return PriorityDispatchResult.CONTINUE
-
- if not callable(migration_context._user_compare_type):
- return PriorityDispatchResult.CONTINUE
-
- is_diff = migration_context._user_compare_type(
- migration_context,
- conn_col,
- metadata_col,
- conn_col.type,
- metadata_col.type,
- )
- if is_diff:
- alter_column_op.modify_type = metadata_col.type
- log.info(
- "Detected type change from %r to %r on '%s.%s'",
- conn_col.type,
- metadata_col.type,
- tname,
- cname,
- )
- return PriorityDispatchResult.STOP
- elif is_diff is False:
- # if user compare type returns False and not None,
- # it means "dont do any more type comparison"
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def _dialect_impl_compare_type(
- autogen_context: AutogenContext,
- alter_column_op: AlterColumnOp,
- schema: Optional[str],
- tname: Union[quoted_name, str],
- cname: Union[quoted_name, str],
- conn_col: Column[Any],
- metadata_col: Column[Any],
-) -> PriorityDispatchResult:
-
- if not _compare_type_setup(
- alter_column_op, tname, cname, conn_col, metadata_col
- ):
- return PriorityDispatchResult.CONTINUE
-
- migration_context = autogen_context.migration_context
- is_diff = migration_context.impl.compare_type(conn_col, metadata_col)
-
- if is_diff:
- alter_column_op.modify_type = metadata_col.type
- log.info(
- "Detected type change from %r to %r on '%s.%s'",
- conn_col.type,
- metadata_col.type,
- tname,
- cname,
- )
- return PriorityDispatchResult.STOP
-
- return PriorityDispatchResult.CONTINUE
-
-
-def setup(plugin: Plugin) -> None:
- plugin.add_autogenerate_comparator(
- _user_compare_type,
- "column",
- "types",
- priority=DispatchPriority.FIRST,
- )
- plugin.add_autogenerate_comparator(
- _dialect_impl_compare_type,
- "column",
- "types",
- priority=DispatchPriority.LAST,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py
deleted file mode 100644
index 41829c0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py
+++ /dev/null
@@ -1,314 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-from __future__ import annotations
-
-from typing import Any
-from typing import cast
-from typing import Collection
-from typing import TYPE_CHECKING
-
-from sqlalchemy.sql.elements import conv
-from typing_extensions import Self
-
-from ...util import sqla_compat
-
-if TYPE_CHECKING:
- from sqlalchemy import Table
- from sqlalchemy.engine import Inspector
- from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint
- from sqlalchemy.engine.interfaces import ReflectedIndex
- from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint
- from sqlalchemy.engine.reflection import _ReflectionInfo
-
-_INSP_KEYS = (
- "columns",
- "pk_constraint",
- "foreign_keys",
- "indexes",
- "unique_constraints",
- "table_comment",
- "check_constraints",
- "table_options",
-)
-_CONSTRAINT_INSP_KEYS = (
- "pk_constraint",
- "foreign_keys",
- "indexes",
- "unique_constraints",
- "check_constraints",
-)
-
-
-class _InspectorConv:
- __slots__ = ("inspector",)
-
- def __new__(cls, inspector: Inspector) -> Self:
- obj: Any
- if sqla_compat.sqla_2:
- obj = object.__new__(_SQLA2InspectorConv)
- _SQLA2InspectorConv.__init__(obj, inspector)
- else:
- obj = object.__new__(_LegacyInspectorConv)
- _LegacyInspectorConv.__init__(obj, inspector)
- return cast(Self, obj)
-
- def __init__(self, inspector: Inspector):
- self.inspector = inspector
-
- def pre_cache_tables(
- self,
- schema: str | None,
- tablenames: list[str],
- all_available_tablenames: Collection[str],
- ) -> None:
- pass
-
- def get_unique_constraints(
- self, tname: str, schema: str | None
- ) -> list[ReflectedUniqueConstraint]:
- raise NotImplementedError()
-
- def get_indexes(
- self, tname: str, schema: str | None
- ) -> list[ReflectedIndex]:
- raise NotImplementedError()
-
- def get_foreign_keys(
- self, tname: str, schema: str | None
- ) -> list[ReflectedForeignKeyConstraint]:
- raise NotImplementedError()
-
- def reflect_table(self, table: Table) -> None:
- raise NotImplementedError()
-
-
-class _LegacyInspectorConv(_InspectorConv):
-
- def _apply_reflectinfo_conv(self, consts):
- if not consts:
- return consts
- for const in consts:
- if const["name"] is not None and not isinstance(
- const["name"], conv
- ):
- const["name"] = conv(const["name"])
- return consts
-
- def _apply_constraint_conv(self, consts):
- if not consts:
- return consts
- for const in consts:
- if const.name is not None and not isinstance(const.name, conv):
- const.name = conv(const.name)
- return consts
-
- def get_indexes(
- self, tname: str, schema: str | None
- ) -> list[ReflectedIndex]:
- return self._apply_reflectinfo_conv(
- self.inspector.get_indexes(tname, schema=schema)
- )
-
- def get_unique_constraints(
- self, tname: str, schema: str | None
- ) -> list[ReflectedUniqueConstraint]:
- return self._apply_reflectinfo_conv(
- self.inspector.get_unique_constraints(tname, schema=schema)
- )
-
- def get_foreign_keys(
- self, tname: str, schema: str | None
- ) -> list[ReflectedForeignKeyConstraint]:
- return self._apply_reflectinfo_conv(
- self.inspector.get_foreign_keys(tname, schema=schema)
- )
-
- def reflect_table(self, table: Table) -> None:
- self.inspector.reflect_table(table, include_columns=None)
-
- self._apply_constraint_conv(table.constraints)
- self._apply_constraint_conv(table.indexes)
-
-
-class _SQLA2InspectorConv(_InspectorConv):
-
- def _pre_cache(
- self,
- schema: str | None,
- tablenames: list[str],
- all_available_tablenames: Collection[str],
- info_key: str,
- inspector_method: Any,
- ) -> None:
-
- if info_key in self.inspector.info_cache:
- return
-
- # heuristic vendored from SQLAlchemy 2.0
- # if more than 50% of the tables in the db are in filter_names load all
- # the tables, since it's most likely faster to avoid a filter on that
- # many tables. also if a dialect doesnt have a "multi" method then
- # return the filter names
- if tablenames and all_available_tablenames and len(tablenames) > 100:
- fraction = len(tablenames) / len(all_available_tablenames)
- else:
- fraction = None
-
- if (
- fraction is None
- or fraction <= 0.5
- or not self.inspector.dialect._overrides_default(
- inspector_method.__name__
- )
- ):
- optimized_filter_names = tablenames
- else:
- optimized_filter_names = None
-
- try:
- elements = inspector_method(
- schema=schema, filter_names=optimized_filter_names
- )
- except NotImplementedError:
- self.inspector.info_cache[info_key] = NotImplementedError
- else:
- self.inspector.info_cache[info_key] = elements
-
- def _return_from_cache(
- self,
- tname: str,
- schema: str | None,
- info_key: str,
- inspector_method: Any,
- apply_constraint_conv: bool = False,
- optional=True,
- ) -> Any:
- not_in_cache = object()
-
- if info_key in self.inspector.info_cache:
- cache = self.inspector.info_cache[info_key]
- if cache is NotImplementedError:
- if optional:
- return {}
- else:
- # maintain NotImplementedError as alembic compare
- # uses these to determine classes of construct that it
- # should not compare to DB elements
- raise NotImplementedError()
-
- individual = cache.get((schema, tname), not_in_cache)
-
- if individual is not not_in_cache:
- if apply_constraint_conv and individual is not None:
- return self._apply_reflectinfo_conv(individual)
- else:
- return individual
-
- try:
- data = inspector_method(tname, schema=schema)
- except NotImplementedError:
- if optional:
- return {}
- else:
- raise
-
- if apply_constraint_conv:
- return self._apply_reflectinfo_conv(data)
- else:
- return data
-
- def get_unique_constraints(
- self, tname: str, schema: str | None
- ) -> list[ReflectedUniqueConstraint]:
- return self._return_from_cache(
- tname,
- schema,
- "alembic_unique_constraints",
- self.inspector.get_unique_constraints,
- apply_constraint_conv=True,
- optional=False,
- )
-
- def get_indexes(
- self, tname: str, schema: str | None
- ) -> list[ReflectedIndex]:
- return self._return_from_cache(
- tname,
- schema,
- "alembic_indexes",
- self.inspector.get_indexes,
- apply_constraint_conv=True,
- optional=False,
- )
-
- def get_foreign_keys(
- self, tname: str, schema: str | None
- ) -> list[ReflectedForeignKeyConstraint]:
- return self._return_from_cache(
- tname,
- schema,
- "alembic_foreign_keys",
- self.inspector.get_foreign_keys,
- apply_constraint_conv=True,
- )
-
- def _apply_reflectinfo_conv(self, consts):
- if not consts:
- return consts
- for const in consts if not isinstance(consts, dict) else [consts]:
- if const["name"] is not None and not isinstance(
- const["name"], conv
- ):
- const["name"] = conv(const["name"])
- return consts
-
- def pre_cache_tables(
- self,
- schema: str | None,
- tablenames: list[str],
- all_available_tablenames: Collection[str],
- ) -> None:
- for key in _INSP_KEYS:
- keyname = f"alembic_{key}"
- meth = getattr(self.inspector, f"get_multi_{key}")
-
- self._pre_cache(
- schema,
- tablenames,
- all_available_tablenames,
- keyname,
- meth,
- )
-
- def _make_reflection_info(
- self, tname: str, schema: str | None
- ) -> _ReflectionInfo:
- from sqlalchemy.engine.reflection import _ReflectionInfo
-
- table_key = (schema, tname)
-
- return _ReflectionInfo(
- unreflectable={},
- **{
- key: {
- table_key: self._return_from_cache(
- tname,
- schema,
- f"alembic_{key}",
- getattr(self.inspector, f"get_{key}"),
- apply_constraint_conv=(key in _CONSTRAINT_INSP_KEYS),
- )
- }
- for key in _INSP_KEYS
- },
- )
-
- def reflect_table(self, table: Table) -> None:
- ri = self._make_reflection_info(table.name, table.schema)
-
- self.inspector.reflect_table(
- table,
- include_columns=None,
- resolve_fks=False,
- _reflect_info=ri,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/render.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/render.py
deleted file mode 100644
index 7f32838..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/render.py
+++ /dev/null
@@ -1,1172 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-from io import StringIO
-import re
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from mako.pygen import PythonPrinter
-from sqlalchemy import schema as sa_schema
-from sqlalchemy import sql
-from sqlalchemy import types as sqltypes
-from sqlalchemy.sql.base import _DialectArgView
-from sqlalchemy.sql.elements import conv
-from sqlalchemy.sql.elements import Label
-from sqlalchemy.sql.elements import quoted_name
-
-from .. import util
-from ..operations import ops
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy import Computed
- from sqlalchemy import Identity
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.elements import TextClause
- from sqlalchemy.sql.schema import CheckConstraint
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.schema import FetchedValue
- from sqlalchemy.sql.schema import ForeignKey
- from sqlalchemy.sql.schema import ForeignKeyConstraint
- from sqlalchemy.sql.schema import Index
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import PrimaryKeyConstraint
- from sqlalchemy.sql.schema import UniqueConstraint
- from sqlalchemy.sql.sqltypes import ARRAY
- from sqlalchemy.sql.type_api import TypeEngine
-
- from alembic.autogenerate.api import AutogenContext
- from alembic.config import Config
- from alembic.operations.ops import MigrationScript
- from alembic.operations.ops import ModifyTableOps
-
-
-MAX_PYTHON_ARGS = 255
-
-
-def _render_gen_name(
- autogen_context: AutogenContext,
- name: sqla_compat._ConstraintName,
-) -> Optional[Union[quoted_name, str, _f_name]]:
- if isinstance(name, conv):
- return _f_name(_alembic_autogenerate_prefix(autogen_context), name)
- else:
- return sqla_compat.constraint_name_or_none(name)
-
-
-def _indent(text: str) -> str:
- text = re.compile(r"^", re.M).sub(" ", text).strip()
- text = re.compile(r" +$", re.M).sub("", text)
- return text
-
-
-def _render_python_into_templatevars(
- autogen_context: AutogenContext,
- migration_script: MigrationScript,
- template_args: Dict[str, Union[str, Config]],
-) -> None:
- imports = autogen_context.imports
-
- for upgrade_ops, downgrade_ops in zip(
- migration_script.upgrade_ops_list, migration_script.downgrade_ops_list
- ):
- template_args[upgrade_ops.upgrade_token] = _indent(
- _render_cmd_body(upgrade_ops, autogen_context)
- )
- template_args[downgrade_ops.downgrade_token] = _indent(
- _render_cmd_body(downgrade_ops, autogen_context)
- )
- template_args["imports"] = "\n".join(sorted(imports))
-
-
-default_renderers = renderers = util.Dispatcher()
-
-
-def _render_cmd_body(
- op_container: ops.OpContainer,
- autogen_context: AutogenContext,
-) -> str:
- buf = StringIO()
- printer = PythonPrinter(buf)
-
- printer.writeline(
- "# ### commands auto generated by Alembic - please adjust! ###"
- )
-
- has_lines = False
- for op in op_container.ops:
- lines = render_op(autogen_context, op)
- has_lines = has_lines or bool(lines)
-
- for line in lines:
- printer.writeline(line)
-
- if not has_lines:
- printer.writeline("pass")
-
- printer.writeline("# ### end Alembic commands ###")
-
- return buf.getvalue()
-
-
-def render_op(
- autogen_context: AutogenContext, op: ops.MigrateOperation
-) -> List[str]:
- renderer = renderers.dispatch(op)
- lines = util.to_list(renderer(autogen_context, op))
- return lines
-
-
-def render_op_text(
- autogen_context: AutogenContext, op: ops.MigrateOperation
-) -> str:
- return "\n".join(render_op(autogen_context, op))
-
-
-@renderers.dispatch_for(ops.ModifyTableOps)
-def _render_modify_table(
- autogen_context: AutogenContext, op: ModifyTableOps
-) -> List[str]:
- opts = autogen_context.opts
- render_as_batch = opts.get("render_as_batch", False)
-
- if op.ops:
- lines = []
- if render_as_batch:
- with autogen_context._within_batch():
- lines.append(
- "with op.batch_alter_table(%r, schema=%r) as batch_op:"
- % (op.table_name, op.schema)
- )
- for t_op in op.ops:
- t_lines = render_op(autogen_context, t_op)
- lines.extend(t_lines)
- lines.append("")
- else:
- for t_op in op.ops:
- t_lines = render_op(autogen_context, t_op)
- lines.extend(t_lines)
-
- return lines
- else:
- return []
-
-
-@renderers.dispatch_for(ops.CreateTableCommentOp)
-def _render_create_table_comment(
- autogen_context: AutogenContext, op: ops.CreateTableCommentOp
-) -> str:
- if autogen_context._has_batch:
- templ = (
- "{prefix}create_table_comment(\n"
- "{indent}{comment},\n"
- "{indent}existing_comment={existing}\n"
- ")"
- )
- else:
- templ = (
- "{prefix}create_table_comment(\n"
- "{indent}'{tname}',\n"
- "{indent}{comment},\n"
- "{indent}existing_comment={existing},\n"
- "{indent}schema={schema}\n"
- ")"
- )
- return templ.format(
- prefix=_alembic_autogenerate_prefix(autogen_context),
- tname=op.table_name,
- comment="%r" % op.comment if op.comment is not None else None,
- existing=(
- "%r" % op.existing_comment
- if op.existing_comment is not None
- else None
- ),
- schema="'%s'" % op.schema if op.schema is not None else None,
- indent=" ",
- )
-
-
-@renderers.dispatch_for(ops.DropTableCommentOp)
-def _render_drop_table_comment(
- autogen_context: AutogenContext, op: ops.DropTableCommentOp
-) -> str:
- if autogen_context._has_batch:
- templ = (
- "{prefix}drop_table_comment(\n"
- "{indent}existing_comment={existing}\n"
- ")"
- )
- else:
- templ = (
- "{prefix}drop_table_comment(\n"
- "{indent}'{tname}',\n"
- "{indent}existing_comment={existing},\n"
- "{indent}schema={schema}\n"
- ")"
- )
- return templ.format(
- prefix=_alembic_autogenerate_prefix(autogen_context),
- tname=op.table_name,
- existing=(
- "%r" % op.existing_comment
- if op.existing_comment is not None
- else None
- ),
- schema="'%s'" % op.schema if op.schema is not None else None,
- indent=" ",
- )
-
-
-@renderers.dispatch_for(ops.CreateTableOp)
-def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str:
- table = op.to_table()
-
- args = [
- col
- for col in [
- _render_column(col, autogen_context) for col in table.columns
- ]
- if col
- ] + sorted(
- [
- rcons
- for rcons in [
- _render_constraint(
- cons, autogen_context, op._namespace_metadata
- )
- for cons in table.constraints
- ]
- if rcons is not None
- ]
- )
-
- if len(args) > MAX_PYTHON_ARGS:
- args_str = "*[" + ",\n".join(args) + "]"
- else:
- args_str = ",\n".join(args)
-
- text = "%(prefix)screate_table(%(tablename)r,\n%(args)s" % {
- "tablename": _ident(op.table_name),
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "args": args_str,
- }
- if op.schema:
- text += ",\nschema=%r" % _ident(op.schema)
-
- comment = table.comment
- if comment:
- text += ",\ncomment=%r" % _ident(comment)
-
- info = table.info
- if info:
- text += f",\ninfo={info!r}"
-
- for k in sorted(op.kw):
- text += ",\n%s=%r" % (k.replace(" ", "_"), op.kw[k])
-
- if table._prefixes:
- prefixes = ", ".join("'%s'" % p for p in table._prefixes)
- text += ",\nprefixes=[%s]" % prefixes
-
- if op.if_not_exists is not None:
- text += ",\nif_not_exists=%r" % bool(op.if_not_exists)
-
- text += "\n)"
- return text
-
-
-@renderers.dispatch_for(ops.DropTableOp)
-def _drop_table(autogen_context: AutogenContext, op: ops.DropTableOp) -> str:
- text = "%(prefix)sdrop_table(%(tname)r" % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "tname": _ident(op.table_name),
- }
- if op.schema:
- text += ", schema=%r" % _ident(op.schema)
-
- if op.if_exists is not None:
- text += ", if_exists=%r" % bool(op.if_exists)
-
- text += ")"
- return text
-
-
-def _render_dialect_kwargs_items(
- autogen_context: AutogenContext, dialect_kwargs: _DialectArgView
-) -> list[str]:
- return [
- f"{key}={_render_potential_expr(val, autogen_context)}"
- for key, val in dialect_kwargs.items()
- ]
-
-
-@renderers.dispatch_for(ops.CreateIndexOp)
-def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str:
- index = op.to_index()
-
- has_batch = autogen_context._has_batch
-
- if has_batch:
- tmpl = (
- "%(prefix)screate_index(%(name)r, [%(columns)s], "
- "unique=%(unique)r%(kwargs)s)"
- )
- else:
- tmpl = (
- "%(prefix)screate_index(%(name)r, %(table)r, [%(columns)s], "
- "unique=%(unique)r%(schema)s%(kwargs)s)"
- )
-
- assert index.table is not None
-
- opts = _render_dialect_kwargs_items(autogen_context, index.dialect_kwargs)
- if op.if_not_exists is not None:
- opts.append("if_not_exists=%r" % bool(op.if_not_exists))
- text = tmpl % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "name": _render_gen_name(autogen_context, index.name),
- "table": _ident(index.table.name),
- "columns": ", ".join(
- _get_index_rendered_expressions(index, autogen_context)
- ),
- "unique": index.unique or False,
- "schema": (
- (", schema=%r" % _ident(index.table.schema))
- if index.table.schema
- else ""
- ),
- "kwargs": ", " + ", ".join(opts) if opts else "",
- }
- return text
-
-
-@renderers.dispatch_for(ops.DropIndexOp)
-def _drop_index(autogen_context: AutogenContext, op: ops.DropIndexOp) -> str:
- index = op.to_index()
-
- has_batch = autogen_context._has_batch
-
- if has_batch:
- tmpl = "%(prefix)sdrop_index(%(name)r%(kwargs)s)"
- else:
- tmpl = (
- "%(prefix)sdrop_index(%(name)r, "
- "table_name=%(table_name)r%(schema)s%(kwargs)s)"
- )
- opts = _render_dialect_kwargs_items(autogen_context, index.dialect_kwargs)
- if op.if_exists is not None:
- opts.append("if_exists=%r" % bool(op.if_exists))
- text = tmpl % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "name": _render_gen_name(autogen_context, op.index_name),
- "table_name": _ident(op.table_name),
- "schema": ((", schema=%r" % _ident(op.schema)) if op.schema else ""),
- "kwargs": ", " + ", ".join(opts) if opts else "",
- }
- return text
-
-
-@renderers.dispatch_for(ops.CreateUniqueConstraintOp)
-def _add_unique_constraint(
- autogen_context: AutogenContext, op: ops.CreateUniqueConstraintOp
-) -> List[str]:
- return [_uq_constraint(op.to_constraint(), autogen_context, True)]
-
-
-@renderers.dispatch_for(ops.CreateForeignKeyOp)
-def _add_fk_constraint(
- autogen_context: AutogenContext, op: ops.CreateForeignKeyOp
-) -> str:
- constraint = op.to_constraint()
- args = [repr(_render_gen_name(autogen_context, op.constraint_name))]
- if not autogen_context._has_batch:
- args.append(repr(_ident(op.source_table)))
-
- args.extend(
- [
- repr(_ident(op.referent_table)),
- repr([_ident(col) for col in op.local_cols]),
- repr([_ident(col) for col in op.remote_cols]),
- ]
- )
- kwargs = [
- "referent_schema",
- "onupdate",
- "ondelete",
- "initially",
- "deferrable",
- "use_alter",
- "match",
- ]
- if not autogen_context._has_batch:
- kwargs.insert(0, "source_schema")
-
- for k in kwargs:
- if k in op.kw:
- value = op.kw[k]
- if value is not None:
- args.append("%s=%r" % (k, value))
-
- dialect_kwargs = _render_dialect_kwargs_items(
- autogen_context, constraint.dialect_kwargs
- )
-
- return "%(prefix)screate_foreign_key(%(args)s%(dialect_kwargs)s)" % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "args": ", ".join(args),
- "dialect_kwargs": (
- ", " + ", ".join(dialect_kwargs) if dialect_kwargs else ""
- ),
- }
-
-
-@renderers.dispatch_for(ops.CreatePrimaryKeyOp)
-def _add_pk_constraint(constraint, autogen_context):
- raise NotImplementedError()
-
-
-@renderers.dispatch_for(ops.CreateCheckConstraintOp)
-def _add_check_constraint(constraint, autogen_context):
- raise NotImplementedError()
-
-
-@renderers.dispatch_for(ops.DropConstraintOp)
-def _drop_constraint(
- autogen_context: AutogenContext, op: ops.DropConstraintOp
-) -> str:
- prefix = _alembic_autogenerate_prefix(autogen_context)
- name = _render_gen_name(autogen_context, op.constraint_name)
- schema = _ident(op.schema) if op.schema else None
- type_ = _ident(op.constraint_type) if op.constraint_type else None
- if_exists = op.if_exists
- params_strs = []
- params_strs.append(repr(name))
- if not autogen_context._has_batch:
- params_strs.append(repr(_ident(op.table_name)))
- if schema is not None:
- params_strs.append(f"schema={schema!r}")
- if type_ is not None:
- params_strs.append(f"type_={type_!r}")
- if if_exists is not None:
- params_strs.append(f"if_exists={if_exists}")
-
- return f"{prefix}drop_constraint({', '.join(params_strs)})"
-
-
-@renderers.dispatch_for(ops.AddColumnOp)
-def _add_column(autogen_context: AutogenContext, op: ops.AddColumnOp) -> str:
- schema, tname, column, if_not_exists = (
- op.schema,
- op.table_name,
- op.column,
- op.if_not_exists,
- )
- if autogen_context._has_batch:
- template = "%(prefix)sadd_column(%(column)s)"
- else:
- template = "%(prefix)sadd_column(%(tname)r, %(column)s"
- if schema:
- template += ", schema=%(schema)r"
- if if_not_exists is not None:
- template += ", if_not_exists=%(if_not_exists)r"
- template += ")"
- text = template % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "tname": tname,
- "column": _render_column(column, autogen_context),
- "schema": schema,
- "if_not_exists": if_not_exists,
- }
- return text
-
-
-@renderers.dispatch_for(ops.DropColumnOp)
-def _drop_column(autogen_context: AutogenContext, op: ops.DropColumnOp) -> str:
- schema, tname, column_name, if_exists = (
- op.schema,
- op.table_name,
- op.column_name,
- op.if_exists,
- )
-
- if autogen_context._has_batch:
- template = "%(prefix)sdrop_column(%(cname)r)"
- else:
- template = "%(prefix)sdrop_column(%(tname)r, %(cname)r"
- if schema:
- template += ", schema=%(schema)r"
- if if_exists is not None:
- template += ", if_exists=%(if_exists)r"
- template += ")"
-
- text = template % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "tname": _ident(tname),
- "cname": _ident(column_name),
- "schema": _ident(schema),
- "if_exists": if_exists,
- }
- return text
-
-
-@renderers.dispatch_for(ops.AlterColumnOp)
-def _alter_column(
- autogen_context: AutogenContext, op: ops.AlterColumnOp
-) -> str:
- tname = op.table_name
- cname = op.column_name
- server_default = op.modify_server_default
- type_ = op.modify_type
- nullable = op.modify_nullable
- comment = op.modify_comment
- newname = op.modify_name
- autoincrement = op.kw.get("autoincrement", None)
- existing_type = op.existing_type
- existing_nullable = op.existing_nullable
- existing_comment = op.existing_comment
- existing_server_default = op.existing_server_default
- schema = op.schema
-
- indent = " " * 11
-
- if autogen_context._has_batch:
- template = "%(prefix)salter_column(%(cname)r"
- else:
- template = "%(prefix)salter_column(%(tname)r, %(cname)r"
-
- text = template % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "tname": tname,
- "cname": cname,
- }
- if existing_type is not None:
- text += ",\n%sexisting_type=%s" % (
- indent,
- _repr_type(existing_type, autogen_context),
- )
- if server_default is not False:
- rendered = _render_server_default(server_default, autogen_context)
- text += ",\n%sserver_default=%s" % (indent, rendered)
-
- if newname is not None:
- text += ",\n%snew_column_name=%r" % (indent, newname)
- if type_ is not None:
- text += ",\n%stype_=%s" % (indent, _repr_type(type_, autogen_context))
- if nullable is not None:
- text += ",\n%snullable=%r" % (indent, nullable)
- if comment is not False:
- text += ",\n%scomment=%r" % (indent, comment)
- if existing_comment is not None:
- text += ",\n%sexisting_comment=%r" % (indent, existing_comment)
- if nullable is None and existing_nullable is not None:
- text += ",\n%sexisting_nullable=%r" % (indent, existing_nullable)
- if autoincrement is not None:
- text += ",\n%sautoincrement=%r" % (indent, autoincrement)
- if server_default is False and existing_server_default:
- rendered = _render_server_default(
- existing_server_default, autogen_context
- )
- text += ",\n%sexisting_server_default=%s" % (indent, rendered)
- if schema and not autogen_context._has_batch:
- text += ",\n%sschema=%r" % (indent, schema)
- text += ")"
- return text
-
-
-class _f_name:
- def __init__(self, prefix: str, name: conv) -> None:
- self.prefix = prefix
- self.name = name
-
- def __repr__(self) -> str:
- return "%sf(%r)" % (self.prefix, _ident(self.name))
-
-
-def _ident(name: Optional[Union[quoted_name, str]]) -> Optional[str]:
- """produce a __repr__() object for a string identifier that may
- use quoted_name() in SQLAlchemy 0.9 and greater.
-
- The issue worked around here is that quoted_name() doesn't have
- very good repr() behavior by itself when unicode is involved.
-
- """
- if name is None:
- return name
- elif isinstance(name, quoted_name):
- return str(name)
- elif isinstance(name, str):
- return name
-
-
-def _render_potential_expr(
- value: Any,
- autogen_context: AutogenContext,
- *,
- wrap_in_element: bool = True,
- is_server_default: bool = False,
- is_index: bool = False,
-) -> str:
- if isinstance(value, sql.ClauseElement):
- sql_text = autogen_context.migration_context.impl.render_ddl_sql_expr(
- value, is_server_default=is_server_default, is_index=is_index
- )
- if wrap_in_element:
- prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
- element = "literal_column" if is_index else "text"
- value_str = f"{prefix}{element}({sql_text!r})"
- if (
- is_index
- and isinstance(value, Label)
- and type(value.name) is str
- ):
- return value_str + f".label({value.name!r})"
- else:
- return value_str
- else:
- return repr(sql_text)
- else:
- return repr(value)
-
-
-def _get_index_rendered_expressions(
- idx: Index, autogen_context: AutogenContext
-) -> List[str]:
- return [
- (
- repr(_ident(getattr(exp, "name", None)))
- if isinstance(exp, sa_schema.Column)
- else _render_potential_expr(exp, autogen_context, is_index=True)
- )
- for exp in idx.expressions
- ]
-
-
-def _uq_constraint(
- constraint: UniqueConstraint,
- autogen_context: AutogenContext,
- alter: bool,
-) -> str:
- opts: List[Tuple[str, Any]] = []
-
- has_batch = autogen_context._has_batch
-
- if constraint.deferrable:
- opts.append(("deferrable", constraint.deferrable))
- if constraint.initially:
- opts.append(("initially", constraint.initially))
- if not has_batch and alter and constraint.table.schema:
- opts.append(("schema", _ident(constraint.table.schema)))
- if not alter and constraint.name:
- opts.append(
- ("name", _render_gen_name(autogen_context, constraint.name))
- )
- dialect_options = _render_dialect_kwargs_items(
- autogen_context, constraint.dialect_kwargs
- )
-
- if alter:
- args = [repr(_render_gen_name(autogen_context, constraint.name))]
- if not has_batch:
- args += [repr(_ident(constraint.table.name))]
- args.append(repr([_ident(col.name) for col in constraint.columns]))
- args.extend(["%s=%r" % (k, v) for k, v in opts])
- args.extend(dialect_options)
- return "%(prefix)screate_unique_constraint(%(args)s)" % {
- "prefix": _alembic_autogenerate_prefix(autogen_context),
- "args": ", ".join(args),
- }
- else:
- args = [repr(_ident(col.name)) for col in constraint.columns]
- args.extend(["%s=%r" % (k, v) for k, v in opts])
- args.extend(dialect_options)
- return "%(prefix)sUniqueConstraint(%(args)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "args": ", ".join(args),
- }
-
-
-def _user_autogenerate_prefix(autogen_context, target):
- prefix = autogen_context.opts["user_module_prefix"]
- if prefix is None:
- return "%s." % target.__module__
- else:
- return prefix
-
-
-def _sqlalchemy_autogenerate_prefix(autogen_context: AutogenContext) -> str:
- return autogen_context.opts["sqlalchemy_module_prefix"] or ""
-
-
-def _alembic_autogenerate_prefix(autogen_context: AutogenContext) -> str:
- if autogen_context._has_batch:
- return "batch_op."
- else:
- return autogen_context.opts["alembic_module_prefix"] or ""
-
-
-def _user_defined_render(
- type_: str, object_: Any, autogen_context: AutogenContext
-) -> Union[str, Literal[False]]:
- if "render_item" in autogen_context.opts:
- render = autogen_context.opts["render_item"]
- if render:
- rendered = render(type_, object_, autogen_context)
- if rendered is not False:
- return rendered
- return False
-
-
-def _render_column(
- column: Column[Any], autogen_context: AutogenContext
-) -> str:
- rendered = _user_defined_render("column", column, autogen_context)
- if rendered is not False:
- return rendered
-
- args: List[str] = []
- opts: List[Tuple[str, Any]] = []
-
- if column.server_default:
- rendered = _render_server_default( # type:ignore[assignment]
- column.server_default, autogen_context
- )
- if rendered:
- if _should_render_server_default_positionally(
- column.server_default
- ):
- args.append(rendered)
- else:
- opts.append(("server_default", rendered))
-
- if (
- column.autoincrement is not None
- and column.autoincrement != sqla_compat.AUTOINCREMENT_DEFAULT
- ):
- opts.append(("autoincrement", column.autoincrement))
-
- if column.nullable is not None:
- opts.append(("nullable", column.nullable))
-
- if column.system:
- opts.append(("system", column.system))
-
- comment = column.comment
- if comment:
- opts.append(("comment", "%r" % comment))
-
- # TODO: for non-ascii colname, assign a "key"
- return "%(prefix)sColumn(%(name)r, %(type)s, %(args)s%(kwargs)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "name": _ident(column.name),
- "type": _repr_type(column.type, autogen_context),
- "args": ", ".join([str(arg) for arg in args]) + ", " if args else "",
- "kwargs": (
- ", ".join(
- ["%s=%s" % (kwname, val) for kwname, val in opts]
- + [
- "%s=%s"
- % (key, _render_potential_expr(val, autogen_context))
- for key, val in column.kwargs.items()
- ]
- )
- ),
- }
-
-
-def _should_render_server_default_positionally(server_default: Any) -> bool:
- return sqla_compat._server_default_is_computed(
- server_default
- ) or sqla_compat._server_default_is_identity(server_default)
-
-
-def _render_server_default(
- default: Optional[
- Union[FetchedValue, str, TextClause, ColumnElement[Any]]
- ],
- autogen_context: AutogenContext,
- repr_: bool = True,
-) -> Optional[str]:
- rendered = _user_defined_render("server_default", default, autogen_context)
- if rendered is not False:
- return rendered
-
- if sqla_compat._server_default_is_computed(default):
- return _render_computed(cast("Computed", default), autogen_context)
- elif sqla_compat._server_default_is_identity(default):
- return _render_identity(cast("Identity", default), autogen_context)
- elif isinstance(default, sa_schema.DefaultClause):
- if isinstance(default.arg, str):
- default = default.arg
- else:
- return _render_potential_expr(
- default.arg, autogen_context, is_server_default=True
- )
- elif isinstance(default, sa_schema.FetchedValue):
- return _render_fetched_value(autogen_context)
-
- if isinstance(default, str) and repr_:
- default = repr(re.sub(r"^'|'$", "", default))
-
- return cast(str, default)
-
-
-def _render_computed(
- computed: Computed, autogen_context: AutogenContext
-) -> str:
- text = _render_potential_expr(
- computed.sqltext, autogen_context, wrap_in_element=False
- )
-
- kwargs = {}
- if computed.persisted is not None:
- kwargs["persisted"] = computed.persisted
- return "%(prefix)sComputed(%(text)s, %(kwargs)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "text": text,
- "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())),
- }
-
-
-def _render_identity(
- identity: Identity, autogen_context: AutogenContext
-) -> str:
- kwargs = sqla_compat._get_identity_options_dict(
- identity, dialect_kwargs=True
- )
-
- return "%(prefix)sIdentity(%(kwargs)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())),
- }
-
-
-def _render_fetched_value(autogen_context: AutogenContext) -> str:
- return "%(prefix)sFetchedValue()" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- }
-
-
-def _repr_type(
- type_: TypeEngine,
- autogen_context: AutogenContext,
- _skip_variants: bool = False,
-) -> str:
- rendered = _user_defined_render("type", type_, autogen_context)
- if rendered is not False:
- return rendered
-
- if hasattr(autogen_context.migration_context, "impl"):
- impl_rt = autogen_context.migration_context.impl.render_type(
- type_, autogen_context
- )
- else:
- impl_rt = None
-
- mod = type(type_).__module__
- imports = autogen_context.imports
-
- if not _skip_variants and sqla_compat._type_has_variants(type_):
- return _render_Variant_type(type_, autogen_context)
- elif mod.startswith("sqlalchemy.dialects"):
- match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod)
- assert match is not None
- dname = match.group(1)
- if imports is not None:
- imports.add("from sqlalchemy.dialects import %s" % dname)
- if impl_rt:
- return impl_rt
- else:
- return "%s.%r" % (dname, type_)
- elif impl_rt:
- return impl_rt
- elif mod.startswith("sqlalchemy."):
- if "_render_%s_type" % type_.__visit_name__ in globals():
- fn = globals()["_render_%s_type" % type_.__visit_name__]
- return fn(type_, autogen_context)
- else:
- prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
- return "%s%r" % (prefix, type_)
- else:
- prefix = _user_autogenerate_prefix(autogen_context, type_)
- return "%s%r" % (prefix, type_)
-
-
-def _render_ARRAY_type(type_: ARRAY, autogen_context: AutogenContext) -> str:
- return cast(
- str,
- _render_type_w_subtype(
- type_, autogen_context, "item_type", r"(.+?\()"
- ),
- )
-
-
-def _render_Variant_type(
- type_: TypeEngine, autogen_context: AutogenContext
-) -> str:
- base_type, variant_mapping = sqla_compat._get_variant_mapping(type_)
- base = _repr_type(base_type, autogen_context, _skip_variants=True)
- assert base is not None and base is not False # type: ignore[comparison-overlap] # noqa:E501
- for dialect in sorted(variant_mapping):
- typ = variant_mapping[dialect]
- base += ".with_variant(%s, %r)" % (
- _repr_type(typ, autogen_context, _skip_variants=True),
- dialect,
- )
- return base
-
-
-def _render_type_w_subtype(
- type_: TypeEngine,
- autogen_context: AutogenContext,
- attrname: str,
- regexp: str,
- prefix: Optional[str] = None,
-) -> Union[Optional[str], Literal[False]]:
- outer_repr = repr(type_)
- inner_type = getattr(type_, attrname, None)
- if inner_type is None:
- return False
-
- inner_repr = repr(inner_type)
-
- inner_repr = re.sub(r"([\(\)])", r"\\\1", inner_repr)
- sub_type = _repr_type(getattr(type_, attrname), autogen_context)
- outer_type = re.sub(regexp + inner_repr, r"\1%s" % sub_type, outer_repr)
-
- if prefix:
- return "%s%s" % (prefix, outer_type)
-
- mod = type(type_).__module__
- if mod.startswith("sqlalchemy.dialects"):
- match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod)
- assert match is not None
- dname = match.group(1)
- return "%s.%s" % (dname, outer_type)
- elif mod.startswith("sqlalchemy"):
- prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
- return "%s%s" % (prefix, outer_type)
- else:
- return None
-
-
-_constraint_renderers = util.Dispatcher()
-
-
-def _render_constraint(
- constraint: Constraint,
- autogen_context: AutogenContext,
- namespace_metadata: Optional[MetaData],
-) -> Optional[str]:
- try:
- renderer = _constraint_renderers.dispatch(constraint)
- except ValueError:
- util.warn("No renderer is established for object %r" % constraint)
- return "[Unknown Python object %r]" % constraint
- else:
- return renderer(constraint, autogen_context, namespace_metadata)
-
-
-@_constraint_renderers.dispatch_for(sa_schema.PrimaryKeyConstraint)
-def _render_primary_key(
- constraint: PrimaryKeyConstraint,
- autogen_context: AutogenContext,
- namespace_metadata: Optional[MetaData],
-) -> Optional[str]:
- rendered = _user_defined_render("primary_key", constraint, autogen_context)
- if rendered is not False:
- return rendered
-
- if not constraint.columns:
- return None
-
- opts = []
- if constraint.name:
- opts.append(
- ("name", repr(_render_gen_name(autogen_context, constraint.name)))
- )
- return "%(prefix)sPrimaryKeyConstraint(%(args)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "args": ", ".join(
- [repr(c.name) for c in constraint.columns]
- + ["%s=%s" % (kwname, val) for kwname, val in opts]
- ),
- }
-
-
-def _fk_colspec(
- fk: ForeignKey,
- metadata_schema: Optional[str],
- namespace_metadata: Optional[MetaData],
-) -> str:
- """Implement a 'safe' version of ForeignKey._get_colspec() that
- won't fail if the remote table can't be resolved.
-
- """
- colspec = fk._get_colspec()
- tokens = colspec.split(".")
- tname, colname = tokens[-2:]
-
- if metadata_schema is not None and len(tokens) == 2:
- table_fullname = "%s.%s" % (metadata_schema, tname)
- else:
- table_fullname = ".".join(tokens[0:-1])
-
- if (
- not fk.link_to_name
- and fk.parent is not None
- and fk.parent.table is not None
- ):
- # try to resolve the remote table in order to adjust for column.key.
- # the FK constraint needs to be rendered in terms of the column
- # name.
-
- if (
- namespace_metadata is not None
- and table_fullname in namespace_metadata.tables
- ):
- col = namespace_metadata.tables[table_fullname].c.get(colname)
- if col is not None:
- colname = _ident(col.name) # type: ignore[assignment]
-
- colspec = "%s.%s" % (table_fullname, colname)
-
- return colspec
-
-
-def _populate_render_fk_opts(
- constraint: ForeignKeyConstraint, opts: List[Tuple[str, str]]
-) -> None:
- if constraint.onupdate:
- opts.append(("onupdate", repr(constraint.onupdate)))
- if constraint.ondelete:
- opts.append(("ondelete", repr(constraint.ondelete)))
- if constraint.initially:
- opts.append(("initially", repr(constraint.initially)))
- if constraint.deferrable:
- opts.append(("deferrable", repr(constraint.deferrable)))
- if constraint.use_alter:
- opts.append(("use_alter", repr(constraint.use_alter)))
- if constraint.match:
- opts.append(("match", repr(constraint.match)))
-
-
-@_constraint_renderers.dispatch_for(sa_schema.ForeignKeyConstraint)
-def _render_foreign_key(
- constraint: ForeignKeyConstraint,
- autogen_context: AutogenContext,
- namespace_metadata: Optional[MetaData],
-) -> Optional[str]:
- rendered = _user_defined_render("foreign_key", constraint, autogen_context)
- if rendered is not False:
- return rendered
-
- opts = []
- if constraint.name:
- opts.append(
- ("name", repr(_render_gen_name(autogen_context, constraint.name)))
- )
-
- _populate_render_fk_opts(constraint, opts)
-
- apply_metadata_schema = (
- namespace_metadata.schema if namespace_metadata is not None else None
- )
- return (
- "%(prefix)sForeignKeyConstraint([%(cols)s], "
- "[%(refcols)s], %(args)s)"
- % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "cols": ", ".join(
- repr(_ident(f.parent.name)) for f in constraint.elements
- ),
- "refcols": ", ".join(
- repr(_fk_colspec(f, apply_metadata_schema, namespace_metadata))
- for f in constraint.elements
- ),
- "args": ", ".join(
- ["%s=%s" % (kwname, val) for kwname, val in opts]
- ),
- }
- )
-
-
-@_constraint_renderers.dispatch_for(sa_schema.UniqueConstraint)
-def _render_unique_constraint(
- constraint: UniqueConstraint,
- autogen_context: AutogenContext,
- namespace_metadata: Optional[MetaData],
-) -> str:
- rendered = _user_defined_render("unique", constraint, autogen_context)
- if rendered is not False:
- return rendered
-
- return _uq_constraint(constraint, autogen_context, False)
-
-
-@_constraint_renderers.dispatch_for(sa_schema.CheckConstraint)
-def _render_check_constraint(
- constraint: CheckConstraint,
- autogen_context: AutogenContext,
- namespace_metadata: Optional[MetaData],
-) -> Optional[str]:
- rendered = _user_defined_render("check", constraint, autogen_context)
- if rendered is not False:
- return rendered
-
- # detect the constraint being part of
- # a parent type which is probably in the Table already.
- # ideally SQLAlchemy would give us more of a first class
- # way to detect this.
- if (
- constraint._create_rule
- and hasattr(constraint._create_rule, "target")
- and isinstance(
- constraint._create_rule.target,
- sqltypes.TypeEngine,
- )
- ):
- return None
- opts = []
- if constraint.name:
- opts.append(
- ("name", repr(_render_gen_name(autogen_context, constraint.name)))
- )
- return "%(prefix)sCheckConstraint(%(sqltext)s%(opts)s)" % {
- "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
- "opts": (
- ", " + (", ".join("%s=%s" % (k, v) for k, v in opts))
- if opts
- else ""
- ),
- "sqltext": _render_potential_expr(
- constraint.sqltext, autogen_context, wrap_in_element=False
- ),
- }
-
-
-@renderers.dispatch_for(ops.ExecuteSQLOp)
-def _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str:
- if not isinstance(op.sqltext, str):
- raise NotImplementedError(
- "Autogenerate rendering of SQL Expression language constructs "
- "not supported here; please use a plain SQL string"
- )
- return "{prefix}execute({sqltext!r})".format(
- prefix=_alembic_autogenerate_prefix(autogen_context),
- sqltext=op.sqltext,
- )
-
-
-renderers = default_renderers.branch()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py b/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py
deleted file mode 100644
index 1d44b5c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py
+++ /dev/null
@@ -1,240 +0,0 @@
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import Iterator
-from typing import List
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import Union
-
-from .. import util
-from ..operations import ops
-
-if TYPE_CHECKING:
- from ..operations.ops import AddColumnOp
- from ..operations.ops import AlterColumnOp
- from ..operations.ops import CreateTableOp
- from ..operations.ops import DowngradeOps
- from ..operations.ops import MigrateOperation
- from ..operations.ops import MigrationScript
- from ..operations.ops import ModifyTableOps
- from ..operations.ops import OpContainer
- from ..operations.ops import UpgradeOps
- from ..runtime.migration import MigrationContext
- from ..script.revision import _GetRevArg
-
-ProcessRevisionDirectiveFn = Callable[
- ["MigrationContext", "_GetRevArg", List["MigrationScript"]], None
-]
-
-
-class Rewriter:
- """A helper object that allows easy 'rewriting' of ops streams.
-
- The :class:`.Rewriter` object is intended to be passed along
- to the
- :paramref:`.EnvironmentContext.configure.process_revision_directives`
- parameter in an ``env.py`` script. Once constructed, any number
- of "rewrites" functions can be associated with it, which will be given
- the opportunity to modify the structure without having to have explicit
- knowledge of the overall structure.
-
- The function is passed the :class:`.MigrationContext` object and
- ``revision`` tuple that are passed to the :paramref:`.Environment
- Context.configure.process_revision_directives` function normally,
- and the third argument is an individual directive of the type
- noted in the decorator. The function has the choice of returning
- a single op directive, which normally can be the directive that
- was actually passed, or a new directive to replace it, or a list
- of zero or more directives to replace it.
-
- .. seealso::
-
- :ref:`autogen_rewriter` - usage example
-
- """
-
- _traverse = util.Dispatcher()
-
- _chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = ()
-
- def __init__(self) -> None:
- self.dispatch = util.Dispatcher()
-
- def chain(
- self,
- other: Union[
- ProcessRevisionDirectiveFn,
- Rewriter,
- ],
- ) -> Rewriter:
- """Produce a "chain" of this :class:`.Rewriter` to another.
-
- This allows two or more rewriters to operate serially on a stream,
- e.g.::
-
- writer1 = autogenerate.Rewriter()
- writer2 = autogenerate.Rewriter()
-
-
- @writer1.rewrites(ops.AddColumnOp)
- def add_column_nullable(context, revision, op):
- op.column.nullable = True
- return op
-
-
- @writer2.rewrites(ops.AddColumnOp)
- def add_column_idx(context, revision, op):
- idx_op = ops.CreateIndexOp(
- "ixc", op.table_name, [op.column.name]
- )
- return [op, idx_op]
-
- writer = writer1.chain(writer2)
-
- :param other: a :class:`.Rewriter` instance
- :return: a new :class:`.Rewriter` that will run the operations
- of this writer, then the "other" writer, in succession.
-
- """
- wr = self.__class__.__new__(self.__class__)
- wr.__dict__.update(self.__dict__)
- wr._chained += (other,)
- return wr
-
- def rewrites(
- self,
- operator: Union[
- Type[AddColumnOp],
- Type[MigrateOperation],
- Type[AlterColumnOp],
- Type[CreateTableOp],
- Type[ModifyTableOps],
- ],
- ) -> Callable[..., Any]:
- """Register a function as rewriter for a given type.
-
- The function should receive three arguments, which are
- the :class:`.MigrationContext`, a ``revision`` tuple, and
- an op directive of the type indicated. E.g.::
-
- @writer1.rewrites(ops.AddColumnOp)
- def add_column_nullable(context, revision, op):
- op.column.nullable = True
- return op
-
- """
- return self.dispatch.dispatch_for(operator)
-
- def _rewrite(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directive: MigrateOperation,
- ) -> Iterator[MigrateOperation]:
- try:
- _rewriter = self.dispatch.dispatch(directive)
- except ValueError:
- _rewriter = None
- yield directive
- else:
- if self in directive._mutations:
- yield directive
- else:
- for r_directive in util.to_list(
- _rewriter(context, revision, directive), []
- ):
- r_directive._mutations = r_directive._mutations.union(
- [self]
- )
- yield r_directive
-
- def __call__(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directives: List[MigrationScript],
- ) -> None:
- self.process_revision_directives(context, revision, directives)
- for process_revision_directives in self._chained:
- process_revision_directives(context, revision, directives)
-
- @_traverse.dispatch_for(ops.MigrationScript)
- def _traverse_script(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directive: MigrationScript,
- ) -> None:
- upgrade_ops_list: List[UpgradeOps] = []
- for upgrade_ops in directive.upgrade_ops_list:
- ret = self._traverse_for(context, revision, upgrade_ops)
- if len(ret) != 1:
- raise ValueError(
- "Can only return single object for UpgradeOps traverse"
- )
- upgrade_ops_list.append(ret[0])
-
- directive.upgrade_ops = upgrade_ops_list
-
- downgrade_ops_list: List[DowngradeOps] = []
- for downgrade_ops in directive.downgrade_ops_list:
- ret = self._traverse_for(context, revision, downgrade_ops)
- if len(ret) != 1:
- raise ValueError(
- "Can only return single object for DowngradeOps traverse"
- )
- downgrade_ops_list.append(ret[0])
- directive.downgrade_ops = downgrade_ops_list
-
- @_traverse.dispatch_for(ops.OpContainer)
- def _traverse_op_container(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directive: OpContainer,
- ) -> None:
- self._traverse_list(context, revision, directive.ops)
-
- @_traverse.dispatch_for(ops.MigrateOperation)
- def _traverse_any_directive(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directive: MigrateOperation,
- ) -> None:
- pass
-
- def _traverse_for(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directive: MigrateOperation,
- ) -> Any:
- directives = list(self._rewrite(context, revision, directive))
- for directive in directives:
- traverser = self._traverse.dispatch(directive)
- traverser(self, context, revision, directive)
- return directives
-
- def _traverse_list(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directives: Any,
- ) -> None:
- dest = []
- for directive in directives:
- dest.extend(self._traverse_for(context, revision, directive))
-
- directives[:] = dest
-
- def process_revision_directives(
- self,
- context: MigrationContext,
- revision: _GetRevArg,
- directives: List[MigrationScript],
- ) -> None:
- self._traverse_list(context, revision, directives)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/command.py b/backend/.venv/lib/python3.12/site-packages/alembic/command.py
deleted file mode 100644
index 4897c0d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/command.py
+++ /dev/null
@@ -1,848 +0,0 @@
-# mypy: allow-untyped-defs, allow-untyped-calls
-
-from __future__ import annotations
-
-import os
-import pathlib
-from typing import List
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from . import autogenerate as autogen
-from . import util
-from .runtime.environment import EnvironmentContext
-from .script import ScriptDirectory
-from .util import compat
-
-if TYPE_CHECKING:
- from alembic.config import Config
- from alembic.script.base import Script
- from alembic.script.revision import _RevIdType
- from .runtime.environment import ProcessRevisionDirectiveFn
-
-
-def list_templates(config: Config) -> None:
- """List available templates.
-
- :param config: a :class:`.Config` object.
-
- """
-
- config.print_stdout("Available templates:\n")
- for tempname in config._get_template_path().iterdir():
- with (tempname / "README").open() as readme:
- synopsis = next(readme).rstrip()
- config.print_stdout("%s - %s", tempname.name, synopsis)
-
- config.print_stdout("\nTemplates are used via the 'init' command, e.g.:")
- config.print_stdout("\n alembic init --template generic ./scripts")
-
-
-def init(
- config: Config,
- directory: str,
- template: str = "generic",
- package: bool = False,
-) -> None:
- """Initialize a new scripts directory.
-
- :param config: a :class:`.Config` object.
-
- :param directory: string path of the target directory.
-
- :param template: string name of the migration environment template to
- use.
-
- :param package: when True, write ``__init__.py`` files into the
- environment location as well as the versions/ location.
-
- """
-
- directory_path = pathlib.Path(directory)
- if directory_path.exists() and list(directory_path.iterdir()):
- raise util.CommandError(
- "Directory %s already exists and is not empty" % directory_path
- )
-
- template_path = config._get_template_path() / template
-
- if not template_path.exists():
- raise util.CommandError(f"No such template {template_path}")
-
- # left as os.access() to suit unit test mocking
- if not os.access(directory_path, os.F_OK):
- with util.status(
- f"Creating directory {directory_path.absolute()}",
- **config.messaging_opts,
- ):
- os.makedirs(directory_path)
-
- versions = directory_path / "versions"
- with util.status(
- f"Creating directory {versions.absolute()}",
- **config.messaging_opts,
- ):
- os.makedirs(versions)
-
- if not directory_path.is_absolute():
- # for non-absolute path, state config file in .ini / pyproject
- # as relative to the %(here)s token, which is where the config
- # file itself would be
-
- if config._config_file_path is not None:
- rel_dir = compat.path_relative_to(
- directory_path.absolute(),
- config._config_file_path.absolute().parent,
- walk_up=True,
- )
- ini_script_location_directory = ("%(here)s" / rel_dir).as_posix()
- if config._toml_file_path is not None:
- rel_dir = compat.path_relative_to(
- directory_path.absolute(),
- config._toml_file_path.absolute().parent,
- walk_up=True,
- )
- toml_script_location_directory = ("%(here)s" / rel_dir).as_posix()
-
- else:
- ini_script_location_directory = directory_path.as_posix()
- toml_script_location_directory = directory_path.as_posix()
-
- script = ScriptDirectory(directory_path)
-
- has_toml = False
-
- config_file: pathlib.Path | None = None
-
- for file_path in template_path.iterdir():
- file_ = file_path.name
- if file_ == "alembic.ini.mako":
- assert config.config_file_name is not None
- config_file = pathlib.Path(config.config_file_name).absolute()
- if config_file.exists():
- util.msg(
- f"File {config_file} already exists, skipping",
- **config.messaging_opts,
- )
- else:
- script._generate_template(
- file_path,
- config_file,
- script_location=ini_script_location_directory,
- )
- elif file_ == "pyproject.toml.mako":
- has_toml = True
- assert config._toml_file_path is not None
- toml_path = config._toml_file_path.absolute()
-
- if toml_path.exists():
- # left as open() to suit unit test mocking
- with open(toml_path, "rb") as f:
- toml_data = compat.tomllib.load(f)
- if "tool" in toml_data and "alembic" in toml_data["tool"]:
-
- util.msg(
- f"File {toml_path} already exists "
- "and already has a [tool.alembic] section, "
- "skipping",
- )
- continue
- script._append_template(
- file_path,
- toml_path,
- script_location=toml_script_location_directory,
- )
- else:
- script._generate_template(
- file_path,
- toml_path,
- script_location=toml_script_location_directory,
- )
-
- elif file_path.is_file():
- output_file = directory_path / file_
- script._copy_file(file_path, output_file)
-
- if package:
- for path in [
- directory_path.absolute() / "__init__.py",
- versions.absolute() / "__init__.py",
- ]:
- with util.status(f"Adding {path!s}", **config.messaging_opts):
- # left as open() to suit unit test mocking
- with open(path, "w"):
- pass
-
- assert config_file is not None
-
- if has_toml:
- util.msg(
- f"Please edit configuration settings in {toml_path} and "
- "configuration/connection/logging "
- f"settings in {config_file} before proceeding.",
- **config.messaging_opts,
- )
- else:
- util.msg(
- "Please edit configuration/connection/logging "
- f"settings in {config_file} before proceeding.",
- **config.messaging_opts,
- )
-
-
-def revision(
- config: Config,
- message: Optional[str] = None,
- autogenerate: bool = False,
- sql: bool = False,
- head: str = "head",
- splice: bool = False,
- branch_label: Optional[_RevIdType] = None,
- version_path: Union[str, os.PathLike[str], None] = None,
- rev_id: Optional[str] = None,
- depends_on: Optional[str] = None,
- process_revision_directives: Optional[ProcessRevisionDirectiveFn] = None,
-) -> Union[Optional[Script], List[Optional[Script]]]:
- """Create a new revision file.
-
- :param config: a :class:`.Config` object.
-
- :param message: string message to apply to the revision; this is the
- ``-m`` option to ``alembic revision``.
-
- :param autogenerate: whether or not to autogenerate the script from
- the database; this is the ``--autogenerate`` option to
- ``alembic revision``.
-
- :param sql: whether to dump the script out as a SQL string; when specified,
- the script is dumped to stdout. This is the ``--sql`` option to
- ``alembic revision``.
-
- :param head: head revision to build the new revision upon as a parent;
- this is the ``--head`` option to ``alembic revision``.
-
- :param splice: whether or not the new revision should be made into a
- new head of its own; is required when the given ``head`` is not itself
- a head. This is the ``--splice`` option to ``alembic revision``.
-
- :param branch_label: string label to apply to the branch; this is the
- ``--branch-label`` option to ``alembic revision``.
-
- :param version_path: string symbol identifying a specific version path
- from the configuration; this is the ``--version-path`` option to
- ``alembic revision``.
-
- :param rev_id: optional revision identifier to use instead of having
- one generated; this is the ``--rev-id`` option to ``alembic revision``.
-
- :param depends_on: optional list of "depends on" identifiers; this is the
- ``--depends-on`` option to ``alembic revision``.
-
- :param process_revision_directives: this is a callable that takes the
- same form as the callable described at
- :paramref:`.EnvironmentContext.configure.process_revision_directives`;
- will be applied to the structure generated by the revision process
- where it can be altered programmatically. Note that unlike all
- the other parameters, this option is only available via programmatic
- use of :func:`.command.revision`.
-
- """
-
- script_directory = ScriptDirectory.from_config(config)
-
- command_args = dict(
- message=message,
- autogenerate=autogenerate,
- sql=sql,
- head=head,
- splice=splice,
- branch_label=branch_label,
- version_path=version_path,
- rev_id=rev_id,
- depends_on=depends_on,
- )
- revision_context = autogen.RevisionContext(
- config,
- script_directory,
- command_args,
- process_revision_directives=process_revision_directives,
- )
-
- environment = util.asbool(
- config.get_alembic_option("revision_environment")
- )
-
- if autogenerate:
- environment = True
-
- if sql:
- raise util.CommandError(
- "Using --sql with --autogenerate does not make any sense"
- )
-
- def retrieve_migrations(rev, context):
- revision_context.run_autogenerate(rev, context)
- return []
-
- elif environment:
-
- def retrieve_migrations(rev, context):
- revision_context.run_no_autogenerate(rev, context)
- return []
-
- elif sql:
- raise util.CommandError(
- "Using --sql with the revision command when "
- "revision_environment is not configured does not make any sense"
- )
-
- if environment:
- with EnvironmentContext(
- config,
- script_directory,
- fn=retrieve_migrations,
- as_sql=sql,
- template_args=revision_context.template_args,
- revision_context=revision_context,
- ):
- script_directory.run_env()
-
- # the revision_context now has MigrationScript structure(s) present.
- # these could theoretically be further processed / rewritten *here*,
- # in addition to the hooks present within each run_migrations() call,
- # or at the end of env.py run_migrations_online().
-
- scripts = [script for script in revision_context.generate_scripts()]
- if len(scripts) == 1:
- return scripts[0]
- else:
- return scripts
-
-
-def check(config: "Config") -> None:
- """Check if revision command with autogenerate has pending upgrade ops.
-
- :param config: a :class:`.Config` object.
-
- .. versionadded:: 1.9.0
-
- """
-
- script_directory = ScriptDirectory.from_config(config)
-
- command_args = dict(
- message=None,
- autogenerate=True,
- sql=False,
- head="head",
- splice=False,
- branch_label=None,
- version_path=None,
- rev_id=None,
- depends_on=None,
- )
- revision_context = autogen.RevisionContext(
- config,
- script_directory,
- command_args,
- )
-
- def retrieve_migrations(rev, context):
- revision_context.run_autogenerate(rev, context)
- return []
-
- with EnvironmentContext(
- config,
- script_directory,
- fn=retrieve_migrations,
- as_sql=False,
- template_args=revision_context.template_args,
- revision_context=revision_context,
- ):
- script_directory.run_env()
-
- # the revision_context now has MigrationScript structure(s) present.
-
- migration_script = revision_context.generated_revisions[-1]
- diffs = []
- for upgrade_ops in migration_script.upgrade_ops_list:
- diffs.extend(upgrade_ops.as_diffs())
-
- if diffs:
- raise util.AutogenerateDiffsDetected(
- f"New upgrade operations detected: {diffs}",
- revision_context=revision_context,
- diffs=diffs,
- )
- else:
- config.print_stdout("No new upgrade operations detected.")
-
-
-def merge(
- config: Config,
- revisions: _RevIdType,
- message: Optional[str] = None,
- branch_label: Optional[_RevIdType] = None,
- rev_id: Optional[str] = None,
-) -> Optional[Script]:
- """Merge two revisions together. Creates a new migration file.
-
- :param config: a :class:`.Config` instance
-
- :param revisions: The revisions to merge.
-
- :param message: string message to apply to the revision.
-
- :param branch_label: string label name to apply to the new revision.
-
- :param rev_id: hardcoded revision identifier instead of generating a new
- one.
-
- .. seealso::
-
- :ref:`branches`
-
- """
-
- script = ScriptDirectory.from_config(config)
- template_args = {
- "config": config # Let templates use config for
- # e.g. multiple databases
- }
-
- environment = util.asbool(
- config.get_alembic_option("revision_environment")
- )
-
- if environment:
-
- def nothing(rev, context):
- return []
-
- with EnvironmentContext(
- config,
- script,
- fn=nothing,
- as_sql=False,
- template_args=template_args,
- ):
- script.run_env()
-
- return script.generate_revision(
- rev_id or util.rev_id(),
- message,
- refresh=True,
- head=revisions,
- branch_labels=branch_label,
- **template_args, # type:ignore[arg-type]
- )
-
-
-def upgrade(
- config: Config,
- revision: str,
- sql: bool = False,
- tag: Optional[str] = None,
-) -> None:
- """Upgrade to a later version.
-
- :param config: a :class:`.Config` instance.
-
- :param revision: string revision target or range for --sql mode. May be
- ``"heads"`` to target the most recent revision(s).
-
- :param sql: if True, use ``--sql`` mode.
-
- :param tag: an arbitrary "tag" that can be intercepted by custom
- ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument`
- method.
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- starting_rev = None
- if ":" in revision:
- if not sql:
- raise util.CommandError("Range revision not allowed")
- starting_rev, revision = revision.split(":", 2)
-
- def upgrade(rev, context):
- return script._upgrade_revs(revision, rev)
-
- with EnvironmentContext(
- config,
- script,
- fn=upgrade,
- as_sql=sql,
- starting_rev=starting_rev,
- destination_rev=revision,
- tag=tag,
- ):
- script.run_env()
-
-
-def downgrade(
- config: Config,
- revision: str,
- sql: bool = False,
- tag: Optional[str] = None,
-) -> None:
- """Revert to a previous version.
-
- :param config: a :class:`.Config` instance.
-
- :param revision: string revision target or range for --sql mode. May
- be ``"base"`` to target the first revision.
-
- :param sql: if True, use ``--sql`` mode.
-
- :param tag: an arbitrary "tag" that can be intercepted by custom
- ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument`
- method.
-
- """
-
- script = ScriptDirectory.from_config(config)
- starting_rev = None
- if ":" in revision:
- if not sql:
- raise util.CommandError("Range revision not allowed")
- starting_rev, revision = revision.split(":", 2)
- elif sql:
- raise util.CommandError(
- "downgrade with --sql requires :"
- )
-
- def downgrade(rev, context):
- return script._downgrade_revs(revision, rev)
-
- with EnvironmentContext(
- config,
- script,
- fn=downgrade,
- as_sql=sql,
- starting_rev=starting_rev,
- destination_rev=revision,
- tag=tag,
- ):
- script.run_env()
-
-
-def show(config: Config, rev: str) -> None:
- """Show the revision(s) denoted by the given symbol.
-
- :param config: a :class:`.Config` instance.
-
- :param rev: string revision target. May be ``"current"`` to show the
- revision(s) currently applied in the database.
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- if rev == "current":
-
- def show_current(rev, context):
- for sc in script.get_revisions(rev):
- config.print_stdout(sc.log_entry)
- return []
-
- with EnvironmentContext(config, script, fn=show_current):
- script.run_env()
- else:
- for sc in script.get_revisions(rev):
- config.print_stdout(sc.log_entry)
-
-
-def history(
- config: Config,
- rev_range: Optional[str] = None,
- verbose: bool = False,
- indicate_current: bool = False,
-) -> None:
- """List changeset scripts in chronological order.
-
- :param config: a :class:`.Config` instance.
-
- :param rev_range: string revision range.
-
- :param verbose: output in verbose mode.
-
- :param indicate_current: indicate current revision.
-
- """
- base: Optional[str]
- head: Optional[str]
- script = ScriptDirectory.from_config(config)
- if rev_range is not None:
- if ":" not in rev_range:
- raise util.CommandError(
- "History range requires [start]:[end], " "[start]:, or :[end]"
- )
- base, head = rev_range.strip().split(":")
- else:
- base = head = None
-
- environment = (
- util.asbool(config.get_alembic_option("revision_environment"))
- or indicate_current
- )
-
- def _display_history(config, script, base, head, currents=()):
- for sc in script.walk_revisions(
- base=base or "base", head=head or "heads"
- ):
- if indicate_current:
- sc._db_current_indicator = sc.revision in currents
-
- config.print_stdout(
- sc.cmd_format(
- verbose=verbose,
- include_branches=True,
- include_doc=True,
- include_parents=True,
- )
- )
-
- def _display_history_w_current(config, script, base, head):
- def _display_current_history(rev, context):
- if head == "current":
- _display_history(config, script, base, rev, rev)
- elif base == "current":
- _display_history(config, script, rev, head, rev)
- else:
- _display_history(config, script, base, head, rev)
- return []
-
- with EnvironmentContext(config, script, fn=_display_current_history):
- script.run_env()
-
- if base == "current" or head == "current" or environment:
- _display_history_w_current(config, script, base, head)
- else:
- _display_history(config, script, base, head)
-
-
-def heads(
- config: Config, verbose: bool = False, resolve_dependencies: bool = False
-) -> None:
- """Show current available heads in the script directory.
-
- :param config: a :class:`.Config` instance.
-
- :param verbose: output in verbose mode.
-
- :param resolve_dependencies: treat dependency version as down revisions.
-
- """
-
- script = ScriptDirectory.from_config(config)
- if resolve_dependencies:
- heads = script.get_revisions("heads")
- else:
- heads = script.get_revisions(script.get_heads())
-
- for rev in heads:
- config.print_stdout(
- rev.cmd_format(
- verbose, include_branches=True, tree_indicators=False
- )
- )
-
-
-def branches(config: Config, verbose: bool = False) -> None:
- """Show current branch points.
-
- :param config: a :class:`.Config` instance.
-
- :param verbose: output in verbose mode.
-
- """
- script = ScriptDirectory.from_config(config)
- for sc in script.walk_revisions():
- if sc.is_branch_point:
- config.print_stdout(
- "%s\n%s\n",
- sc.cmd_format(verbose, include_branches=True),
- "\n".join(
- "%s -> %s"
- % (
- " " * len(str(sc.revision)),
- rev_obj.cmd_format(
- False, include_branches=True, include_doc=verbose
- ),
- )
- for rev_obj in (
- script.get_revision(rev) for rev in sc.nextrev
- )
- ),
- )
-
-
-def current(
- config: Config, check_heads: bool = False, verbose: bool = False
-) -> None:
- """Display the current revision for a database.
-
- :param config: a :class:`.Config` instance.
-
- :param check_heads: Check if all head revisions are applied to the
- database. Raises :class:`.DatabaseNotAtHead` if this is not the case.
-
- .. versionadded:: 1.17.1
-
- :param verbose: output in verbose mode.
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- def display_version(rev, context):
- if verbose:
- config.print_stdout(
- "Current revision(s) for %s:",
- util.obfuscate_url_pw(context.connection.engine.url),
- )
- if check_heads and (
- set(context.get_current_heads()) != set(script.get_heads())
- ):
- raise util.DatabaseNotAtHead(
- "Database is not on all head revisions"
- )
- for rev in script.get_all_current(rev):
- config.print_stdout(rev.cmd_format(verbose))
-
- return []
-
- with EnvironmentContext(
- config, script, fn=display_version, dont_mutate=True
- ):
- script.run_env()
-
-
-def stamp(
- config: Config,
- revision: _RevIdType,
- sql: bool = False,
- tag: Optional[str] = None,
- purge: bool = False,
-) -> None:
- """'stamp' the revision table with the given revision; don't
- run any migrations.
-
- :param config: a :class:`.Config` instance.
-
- :param revision: target revision or list of revisions. May be a list
- to indicate stamping of multiple branch heads; may be ``"base"``
- to remove all revisions from the table or ``"heads"`` to stamp the
- most recent revision(s).
-
- .. note:: this parameter is called "revisions" in the command line
- interface.
-
- :param sql: use ``--sql`` mode
-
- :param tag: an arbitrary "tag" that can be intercepted by custom
- ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument`
- method.
-
- :param purge: delete all entries in the version table before stamping.
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- if sql:
- destination_revs = []
- starting_rev = None
- for _revision in util.to_list(revision):
- if ":" in _revision:
- srev, _revision = _revision.split(":", 2)
-
- if starting_rev != srev:
- if starting_rev is None:
- starting_rev = srev
- else:
- raise util.CommandError(
- "Stamp operation with --sql only supports a "
- "single starting revision at a time"
- )
- destination_revs.append(_revision)
- else:
- destination_revs = util.to_list(revision)
-
- def do_stamp(rev, context):
- return script._stamp_revs(util.to_tuple(destination_revs), rev)
-
- with EnvironmentContext(
- config,
- script,
- fn=do_stamp,
- as_sql=sql,
- starting_rev=starting_rev if sql else None,
- destination_rev=util.to_tuple(destination_revs),
- tag=tag,
- purge=purge,
- ):
- script.run_env()
-
-
-def edit(config: Config, rev: str) -> None:
- """Edit revision script(s) using $EDITOR.
-
- :param config: a :class:`.Config` instance.
-
- :param rev: target revision.
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- if rev == "current":
-
- def edit_current(rev, context):
- if not rev:
- raise util.CommandError("No current revisions")
- for sc in script.get_revisions(rev):
- util.open_in_editor(sc.path)
- return []
-
- with EnvironmentContext(config, script, fn=edit_current):
- script.run_env()
- else:
- revs = script.get_revisions(rev)
- if not revs:
- raise util.CommandError(
- "No revision files indicated by symbol '%s'" % rev
- )
- for sc in revs:
- assert sc
- util.open_in_editor(sc.path)
-
-
-def ensure_version(config: Config, sql: bool = False) -> None:
- """Create the alembic version table if it doesn't exist already .
-
- :param config: a :class:`.Config` instance.
-
- :param sql: use ``--sql`` mode.
-
- .. versionadded:: 1.7.6
-
- """
-
- script = ScriptDirectory.from_config(config)
-
- def do_ensure_version(rev, context):
- context._ensure_version_table()
- return []
-
- with EnvironmentContext(
- config,
- script,
- fn=do_ensure_version,
- as_sql=sql,
- ):
- script.run_env()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/config.py b/backend/.venv/lib/python3.12/site-packages/alembic/config.py
deleted file mode 100644
index 121a445..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/config.py
+++ /dev/null
@@ -1,1051 +0,0 @@
-from __future__ import annotations
-
-from argparse import ArgumentParser
-from argparse import Namespace
-from configparser import ConfigParser
-import inspect
-import logging
-import os
-from pathlib import Path
-import re
-import sys
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import Mapping
-from typing import Optional
-from typing import overload
-from typing import Protocol
-from typing import Sequence
-from typing import TextIO
-from typing import Union
-
-from typing_extensions import TypedDict
-
-from . import __version__
-from . import command
-from . import util
-from .util import compat
-from .util.pyfiles import _preserving_path_as_str
-
-
-log = logging.getLogger(__name__)
-
-
-class Config:
- r"""Represent an Alembic configuration.
-
- Within an ``env.py`` script, this is available
- via the :attr:`.EnvironmentContext.config` attribute,
- which in turn is available at ``alembic.context``::
-
- from alembic import context
-
- some_param = context.config.get_main_option("my option")
-
- When invoking Alembic programmatically, a new
- :class:`.Config` can be created by passing
- the name of an .ini file to the constructor::
-
- from alembic.config import Config
- alembic_cfg = Config("/path/to/yourapp/alembic.ini")
-
- With a :class:`.Config` object, you can then
- run Alembic commands programmatically using the directives
- in :mod:`alembic.command`.
-
- The :class:`.Config` object can also be constructed without
- a filename. Values can be set programmatically, and
- new sections will be created as needed::
-
- from alembic.config import Config
- alembic_cfg = Config()
- alembic_cfg.set_main_option("script_location", "myapp:migrations")
- alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar")
- alembic_cfg.set_section_option("mysection", "foo", "bar")
-
- .. warning::
-
- When using programmatic configuration, make sure the
- ``env.py`` file in use is compatible with the target configuration;
- including that the call to Python ``logging.fileConfig()`` is
- omitted if the programmatic configuration doesn't actually include
- logging directives.
-
- For passing non-string values to environments, such as connections and
- engines, use the :attr:`.Config.attributes` dictionary::
-
- with engine.begin() as connection:
- alembic_cfg.attributes['connection'] = connection
- command.upgrade(alembic_cfg, "head")
-
- :param file\_: name of the .ini file to open if an ``alembic.ini`` is
- to be used. This should refer to the ``alembic.ini`` file, either as
- a filename or a full path to the file. This filename if passed must refer
- to an **ini file in ConfigParser format** only.
-
- :param toml\_file: name of the pyproject.toml file to open if a
- ``pyproject.toml`` file is to be used. This should refer to the
- ``pyproject.toml`` file, either as a filename or a full path to the file.
- This file must be in toml format. Both :paramref:`.Config.file\_` and
- :paramref:`.Config.toml\_file` may be passed simultaneously, or
- exclusively.
-
- .. versionadded:: 1.16.0
-
- :param ini_section: name of the main Alembic section within the
- .ini file
- :param output_buffer: optional file-like input buffer which
- will be passed to the :class:`.MigrationContext` - used to redirect
- the output of "offline generation" when using Alembic programmatically.
- :param stdout: buffer where the "print" output of commands will be sent.
- Defaults to ``sys.stdout``.
-
- :param config_args: A dictionary of keys and values that will be used
- for substitution in the alembic config file, as well as the pyproject.toml
- file, depending on which / both are used. The dictionary as given is
- **copied** to two new, independent dictionaries, stored locally under the
- attributes ``.config_args`` and ``.toml_args``. Both of these
- dictionaries will also be populated with the replacement variable
- ``%(here)s``, which refers to the location of the .ini and/or .toml file
- as appropriate.
-
- :param attributes: optional dictionary of arbitrary Python keys/values,
- which will be populated into the :attr:`.Config.attributes` dictionary.
-
- .. seealso::
-
- :ref:`connection_sharing`
-
- """
-
- def __init__(
- self,
- file_: Union[str, os.PathLike[str], None] = None,
- toml_file: Union[str, os.PathLike[str], None] = None,
- ini_section: str = "alembic",
- output_buffer: Optional[TextIO] = None,
- stdout: TextIO = sys.stdout,
- cmd_opts: Optional[Namespace] = None,
- config_args: Mapping[str, Any] = util.immutabledict(),
- attributes: Optional[Dict[str, Any]] = None,
- ) -> None:
- """Construct a new :class:`.Config`"""
- self.config_file_name = (
- _preserving_path_as_str(file_) if file_ else None
- )
- self.toml_file_name = (
- _preserving_path_as_str(toml_file) if toml_file else None
- )
- self.config_ini_section = ini_section
- self.output_buffer = output_buffer
- self.stdout = stdout
- self.cmd_opts = cmd_opts
- self.config_args = dict(config_args)
- self.toml_args = dict(config_args)
- if attributes:
- self.attributes.update(attributes)
-
- cmd_opts: Optional[Namespace] = None
- """The command-line options passed to the ``alembic`` script.
-
- Within an ``env.py`` script this can be accessed via the
- :attr:`.EnvironmentContext.config` attribute.
-
- .. seealso::
-
- :meth:`.EnvironmentContext.get_x_argument`
-
- """
-
- config_file_name: Optional[str] = None
- """Filesystem path to the .ini file in use."""
-
- toml_file_name: Optional[str] = None
- """Filesystem path to the pyproject.toml file in use.
-
- .. versionadded:: 1.16.0
-
- """
-
- @property
- def _config_file_path(self) -> Optional[Path]:
- if self.config_file_name is None:
- return None
- return Path(self.config_file_name)
-
- @property
- def _toml_file_path(self) -> Optional[Path]:
- if self.toml_file_name is None:
- return None
- return Path(self.toml_file_name)
-
- config_ini_section: str = None # type:ignore[assignment]
- """Name of the config file section to read basic configuration
- from. Defaults to ``alembic``, that is the ``[alembic]`` section
- of the .ini file. This value is modified using the ``-n/--name``
- option to the Alembic runner.
-
- """
-
- @util.memoized_property
- def attributes(self) -> Dict[str, Any]:
- """A Python dictionary for storage of additional state.
-
-
- This is a utility dictionary which can include not just strings but
- engines, connections, schema objects, or anything else.
- Use this to pass objects into an env.py script, such as passing
- a :class:`sqlalchemy.engine.base.Connection` when calling
- commands from :mod:`alembic.command` programmatically.
-
- .. seealso::
-
- :ref:`connection_sharing`
-
- :paramref:`.Config.attributes`
-
- """
- return {}
-
- def print_stdout(self, text: str, *arg: Any) -> None:
- """Render a message to standard out.
-
- When :meth:`.Config.print_stdout` is called with additional args
- those arguments will formatted against the provided text,
- otherwise we simply output the provided text verbatim.
-
- This is a no-op when the``quiet`` messaging option is enabled.
-
- e.g.::
-
- >>> config.print_stdout('Some text %s', 'arg')
- Some Text arg
-
- """
-
- if arg:
- output = str(text) % arg
- else:
- output = str(text)
-
- util.write_outstream(self.stdout, output, "\n", **self.messaging_opts)
-
- @util.memoized_property
- def file_config(self) -> ConfigParser:
- """Return the underlying ``ConfigParser`` object.
-
- Dir*-ect access to the .ini file is available here,
- though the :meth:`.Config.get_section` and
- :meth:`.Config.get_main_option`
- methods provide a possibly simpler interface.
-
- """
-
- if self._config_file_path:
- here = self._config_file_path.absolute().parent
- else:
- here = Path()
- self.config_args["here"] = here.as_posix()
- file_config = ConfigParser(self.config_args)
-
- verbose = getattr(self.cmd_opts, "verbose", False)
- if self._config_file_path:
- compat.read_config_parser(file_config, [self._config_file_path])
- if verbose:
- log.info(
- "Loading config from file: %s", self._config_file_path
- )
- else:
- file_config.add_section(self.config_ini_section)
- if verbose:
- log.info(
- "No config file provided; using in-memory default config"
- )
- return file_config
-
- @util.memoized_property
- def toml_alembic_config(self) -> Mapping[str, Any]:
- """Return a dictionary of the [tool.alembic] section from
- pyproject.toml"""
-
- if self._toml_file_path and self._toml_file_path.exists():
-
- here = self._toml_file_path.absolute().parent
- self.toml_args["here"] = here.as_posix()
-
- with open(self._toml_file_path, "rb") as f:
- toml_data = compat.tomllib.load(f)
- data = toml_data.get("tool", {}).get("alembic", {})
- if not isinstance(data, dict):
- raise util.CommandError("Incorrect TOML format")
- return data
-
- else:
- return {}
-
- def get_template_directory(self) -> str:
- """Return the directory where Alembic setup templates are found.
-
- This method is used by the alembic ``init`` and ``list_templates``
- commands.
-
- """
- import alembic
-
- package_dir = Path(alembic.__file__).absolute().parent
- return str(package_dir / "templates")
-
- def _get_template_path(self) -> Path:
- """Return the directory where Alembic setup templates are found.
-
- This method is used by the alembic ``init`` and ``list_templates``
- commands.
-
- .. versionadded:: 1.16.0
-
- """
- return Path(self.get_template_directory())
-
- @overload
- def get_section(
- self, name: str, default: None = ...
- ) -> Optional[Dict[str, str]]: ...
-
- # "default" here could also be a TypeVar
- # _MT = TypeVar("_MT", bound=Mapping[str, str]),
- # however mypy wasn't handling that correctly (pyright was)
- @overload
- def get_section(
- self, name: str, default: Dict[str, str]
- ) -> Dict[str, str]: ...
-
- @overload
- def get_section(
- self, name: str, default: Mapping[str, str]
- ) -> Union[Dict[str, str], Mapping[str, str]]: ...
-
- def get_section(
- self, name: str, default: Optional[Mapping[str, str]] = None
- ) -> Optional[Mapping[str, str]]:
- """Return all the configuration options from a given .ini file section
- as a dictionary.
-
- If the given section does not exist, the value of ``default``
- is returned, which is expected to be a dictionary or other mapping.
-
- """
- if not self.file_config.has_section(name):
- return default
-
- return dict(self.file_config.items(name))
-
- def set_main_option(self, name: str, value: str) -> None:
- """Set an option programmatically within the 'main' section.
-
- This overrides whatever was in the .ini file.
-
- :param name: name of the value
-
- :param value: the value. Note that this value is passed to
- ``ConfigParser.set``, which supports variable interpolation using
- pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of
- an interpolation symbol must therefore be escaped, e.g. ``%%``.
- The given value may refer to another value already in the file
- using the interpolation format.
-
- """
- self.set_section_option(self.config_ini_section, name, value)
-
- def remove_main_option(self, name: str) -> None:
- self.file_config.remove_option(self.config_ini_section, name)
-
- def set_section_option(self, section: str, name: str, value: str) -> None:
- """Set an option programmatically within the given section.
-
- The section is created if it doesn't exist already.
- The value here will override whatever was in the .ini
- file.
-
- Does **NOT** consume from the pyproject.toml file.
-
- .. seealso::
-
- :meth:`.Config.get_alembic_option` - includes pyproject support
-
- :param section: name of the section
-
- :param name: name of the value
-
- :param value: the value. Note that this value is passed to
- ``ConfigParser.set``, which supports variable interpolation using
- pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of
- an interpolation symbol must therefore be escaped, e.g. ``%%``.
- The given value may refer to another value already in the file
- using the interpolation format.
-
- """
-
- if not self.file_config.has_section(section):
- self.file_config.add_section(section)
- self.file_config.set(section, name, value)
-
- def get_section_option(
- self, section: str, name: str, default: Optional[str] = None
- ) -> Optional[str]:
- """Return an option from the given section of the .ini file."""
- if not self.file_config.has_section(section):
- raise util.CommandError(
- "No config file %r found, or file has no "
- "'[%s]' section" % (self.config_file_name, section)
- )
- if self.file_config.has_option(section, name):
- return self.file_config.get(section, name)
- else:
- return default
-
- @overload
- def get_main_option(self, name: str, default: str) -> str: ...
-
- @overload
- def get_main_option(
- self, name: str, default: Optional[str] = None
- ) -> Optional[str]: ...
-
- def get_main_option(
- self, name: str, default: Optional[str] = None
- ) -> Optional[str]:
- """Return an option from the 'main' section of the .ini file.
-
- This defaults to being a key from the ``[alembic]``
- section, unless the ``-n/--name`` flag were used to
- indicate a different section.
-
- Does **NOT** consume from the pyproject.toml file.
-
- .. seealso::
-
- :meth:`.Config.get_alembic_option` - includes pyproject support
-
- """
- return self.get_section_option(self.config_ini_section, name, default)
-
- @overload
- def get_alembic_option(self, name: str, default: str) -> str: ...
-
- @overload
- def get_alembic_option(
- self, name: str, default: Optional[str] = None
- ) -> Optional[str]: ...
-
- def get_alembic_option(
- self, name: str, default: Optional[str] = None
- ) -> Union[
- None, str, list[str], dict[str, str], list[dict[str, str]], int
- ]:
- """Return an option from the "[alembic]" or "[tool.alembic]" section
- of the configparser-parsed .ini file (e.g. ``alembic.ini``) or
- toml-parsed ``pyproject.toml`` file.
-
- The value returned is expected to be None, string, list of strings,
- or dictionary of strings. Within each type of string value, the
- ``%(here)s`` token is substituted out with the absolute path of the
- ``pyproject.toml`` file, as are other tokens which are extracted from
- the :paramref:`.Config.config_args` dictionary.
-
- Searches always prioritize the configparser namespace first, before
- searching in the toml namespace.
-
- If Alembic was run using the ``-n/--name`` flag to indicate an
- alternate main section name, this is taken into account **only** for
- the configparser-parsed .ini file. The section name in toml is always
- ``[tool.alembic]``.
-
-
- .. versionadded:: 1.16.0
-
- """
-
- if self.file_config.has_option(self.config_ini_section, name):
- return self.file_config.get(self.config_ini_section, name)
- else:
- return self._get_toml_config_value(name, default=default)
-
- def get_alembic_boolean_option(self, name: str) -> bool:
- if self.file_config.has_option(self.config_ini_section, name):
- return (
- self.file_config.get(self.config_ini_section, name) == "true"
- )
- else:
- value = self.toml_alembic_config.get(name, False)
- if not isinstance(value, bool):
- raise util.CommandError(
- f"boolean value expected for TOML parameter {name!r}"
- )
- return value
-
- def _get_toml_config_value(
- self, name: str, default: Optional[Any] = None
- ) -> Union[
- None, str, list[str], dict[str, str], list[dict[str, str]], int
- ]:
- USE_DEFAULT = object()
- value: Union[None, str, list[str], dict[str, str], int] = (
- self.toml_alembic_config.get(name, USE_DEFAULT)
- )
- if value is USE_DEFAULT:
- return default
- if value is not None:
- if isinstance(value, str):
- value = value % (self.toml_args)
- elif isinstance(value, list):
- if value and isinstance(value[0], dict):
- value = [
- {k: v % (self.toml_args) for k, v in dv.items()}
- for dv in value
- ]
- else:
- value = cast(
- "list[str]", [v % (self.toml_args) for v in value]
- )
- elif isinstance(value, dict):
- value = cast(
- "dict[str, str]",
- {k: v % (self.toml_args) for k, v in value.items()},
- )
- elif isinstance(value, int):
- return value
- else:
- raise util.CommandError(
- f"unsupported TOML value type for key: {name!r}"
- )
- return value
-
- @util.memoized_property
- def messaging_opts(self) -> MessagingOptions:
- """The messaging options."""
- return cast(
- MessagingOptions,
- util.immutabledict(
- {"quiet": getattr(self.cmd_opts, "quiet", False)}
- ),
- )
-
- def _get_file_separator_char(self, *names: str) -> Optional[str]:
- for name in names:
- separator = self.get_main_option(name)
- if separator is not None:
- break
- else:
- return None
-
- split_on_path = {
- "space": " ",
- "newline": "\n",
- "os": os.pathsep,
- ":": ":",
- ";": ";",
- }
-
- try:
- sep = split_on_path[separator]
- except KeyError as ke:
- raise ValueError(
- "'%s' is not a valid value for %s; "
- "expected 'space', 'newline', 'os', ':', ';'"
- % (separator, name)
- ) from ke
- else:
- if name == "version_path_separator":
- util.warn_deprecated(
- "The version_path_separator configuration parameter "
- "is deprecated; please use path_separator"
- )
- return sep
-
- def get_version_locations_list(self) -> Optional[list[str]]:
-
- version_locations_str = self.file_config.get(
- self.config_ini_section, "version_locations", fallback=None
- )
-
- if version_locations_str:
- split_char = self._get_file_separator_char(
- "path_separator", "version_path_separator"
- )
-
- if split_char is None:
-
- # legacy behaviour for backwards compatibility
- util.warn_deprecated(
- "No path_separator found in configuration; "
- "falling back to legacy splitting on spaces/commas "
- "for version_locations. Consider adding "
- "path_separator=os to Alembic config."
- )
-
- _split_on_space_comma = re.compile(r", *|(?: +)")
- return _split_on_space_comma.split(version_locations_str)
- else:
- return [
- x.strip()
- for x in version_locations_str.split(split_char)
- if x
- ]
- else:
- return cast(
- "list[str]",
- self._get_toml_config_value("version_locations", None),
- )
-
- def get_prepend_sys_paths_list(self) -> Optional[list[str]]:
- prepend_sys_path_str = self.file_config.get(
- self.config_ini_section, "prepend_sys_path", fallback=None
- )
-
- if prepend_sys_path_str:
- split_char = self._get_file_separator_char("path_separator")
-
- if split_char is None:
-
- # legacy behaviour for backwards compatibility
- util.warn_deprecated(
- "No path_separator found in configuration; "
- "falling back to legacy splitting on spaces, commas, "
- "and colons for prepend_sys_path. Consider adding "
- "path_separator=os to Alembic config."
- )
-
- _split_on_space_comma_colon = re.compile(r", *|(?: +)|\:")
- return _split_on_space_comma_colon.split(prepend_sys_path_str)
- else:
- return [
- x.strip()
- for x in prepend_sys_path_str.split(split_char)
- if x
- ]
- else:
- return cast(
- "list[str]",
- self._get_toml_config_value("prepend_sys_path", None),
- )
-
- def get_hooks_list(self) -> list[PostWriteHookConfig]:
-
- hooks: list[PostWriteHookConfig] = []
-
- if not self.file_config.has_section("post_write_hooks"):
- toml_hook_config = cast(
- "list[dict[str, str]]",
- self._get_toml_config_value("post_write_hooks", []),
- )
- for cfg in toml_hook_config:
- opts = dict(cfg)
- opts["_hook_name"] = opts.pop("name")
- hooks.append(opts)
-
- else:
- _split_on_space_comma = re.compile(r", *|(?: +)")
- ini_hook_config = self.get_section("post_write_hooks", {})
- names = _split_on_space_comma.split(
- ini_hook_config.get("hooks", "")
- )
-
- for name in names:
- if not name:
- continue
- opts = {
- key[len(name) + 1 :]: ini_hook_config[key]
- for key in ini_hook_config
- if key.startswith(name + ".")
- }
-
- opts["_hook_name"] = name
- hooks.append(opts)
-
- return hooks
-
-
-PostWriteHookConfig = Mapping[str, str]
-
-
-class MessagingOptions(TypedDict, total=False):
- quiet: bool
-
-
-class CommandFunction(Protocol):
- """A function that may be registered in the CLI as an alembic command.
- It must be a named function and it must accept a :class:`.Config` object
- as the first argument.
-
- .. versionadded:: 1.15.3
-
- """
-
- __name__: str
-
- def __call__(self, config: Config, *args: Any, **kwargs: Any) -> Any: ...
-
-
-class CommandLine:
- """Provides the command line interface to Alembic."""
-
- def __init__(self, prog: Optional[str] = None) -> None:
- self._generate_args(prog)
-
- _KWARGS_OPTS = {
- "template": (
- "-t",
- "--template",
- dict(
- default="generic",
- type=str,
- help="Setup template for use with 'init'",
- ),
- ),
- "message": (
- "-m",
- "--message",
- dict(type=str, help="Message string to use with 'revision'"),
- ),
- "sql": (
- "--sql",
- dict(
- action="store_true",
- help="Don't emit SQL to database - dump to "
- "standard output/file instead. See docs on "
- "offline mode.",
- ),
- ),
- "tag": (
- "--tag",
- dict(
- type=str,
- help="Arbitrary 'tag' name - can be used by "
- "custom env.py scripts.",
- ),
- ),
- "head": (
- "--head",
- dict(
- type=str,
- help="Specify head revision or @head "
- "to base new revision on.",
- ),
- ),
- "splice": (
- "--splice",
- dict(
- action="store_true",
- help="Allow a non-head revision as the 'head' to splice onto",
- ),
- ),
- "depends_on": (
- "--depends-on",
- dict(
- action="append",
- help="Specify one or more revision identifiers "
- "which this revision should depend on.",
- ),
- ),
- "rev_id": (
- "--rev-id",
- dict(
- type=str,
- help="Specify a hardcoded revision id instead of "
- "generating one",
- ),
- ),
- "version_path": (
- "--version-path",
- dict(
- type=str,
- help="Specify specific path from config for version file",
- ),
- ),
- "branch_label": (
- "--branch-label",
- dict(
- type=str,
- help="Specify a branch label to apply to the new revision",
- ),
- ),
- "verbose": (
- "-v",
- "--verbose",
- dict(action="store_true", help="Use more verbose output"),
- ),
- "resolve_dependencies": (
- "--resolve-dependencies",
- dict(
- action="store_true",
- help="Treat dependency versions as down revisions",
- ),
- ),
- "autogenerate": (
- "--autogenerate",
- dict(
- action="store_true",
- help="Populate revision script with candidate "
- "migration operations, based on comparison "
- "of database to model.",
- ),
- ),
- "rev_range": (
- "-r",
- "--rev-range",
- dict(
- action="store",
- help="Specify a revision range; format is [start]:[end]",
- ),
- ),
- "indicate_current": (
- "-i",
- "--indicate-current",
- dict(
- action="store_true",
- help="Indicate the current revision",
- ),
- ),
- "purge": (
- "--purge",
- dict(
- action="store_true",
- help="Unconditionally erase the version table before stamping",
- ),
- ),
- "package": (
- "--package",
- dict(
- action="store_true",
- help="Write empty __init__.py files to the "
- "environment and version locations",
- ),
- ),
- "check_heads": (
- "-c",
- "--check-heads",
- dict(
- action="store_true",
- help=(
- "Check if all head revisions are applied to the database. "
- "Exit with an error code if this is not the case."
- ),
- ),
- ),
- }
- _POSITIONAL_OPTS = {
- "directory": dict(help="location of scripts directory"),
- "revision": dict(
- help="revision identifier",
- ),
- "revisions": dict(
- nargs="+",
- help="one or more revisions, or 'heads' for all heads",
- ),
- }
- _POSITIONAL_TRANSLATIONS: dict[Any, dict[str, str]] = {
- command.stamp: {"revision": "revisions"}
- }
-
- def _generate_args(self, prog: Optional[str]) -> None:
- parser = ArgumentParser(prog=prog)
-
- parser.add_argument(
- "--version", action="version", version="%%(prog)s %s" % __version__
- )
- parser.add_argument(
- "-c",
- "--config",
- action="append",
- help="Alternate config file; defaults to value of "
- 'ALEMBIC_CONFIG environment variable, or "alembic.ini". '
- "May also refer to pyproject.toml file. May be specified twice "
- "to reference both files separately",
- )
- parser.add_argument(
- "-n",
- "--name",
- type=str,
- default="alembic",
- help="Name of section in .ini file to use for Alembic config "
- "(only applies to configparser config, not toml)",
- )
- parser.add_argument(
- "-x",
- action="append",
- help="Additional arguments consumed by "
- "custom env.py scripts, e.g. -x "
- "setting1=somesetting -x setting2=somesetting",
- )
- parser.add_argument(
- "--raiseerr",
- action="store_true",
- help="Raise a full stack trace on error",
- )
- parser.add_argument(
- "-q",
- "--quiet",
- action="store_true",
- help="Do not log to std output.",
- )
-
- self.subparsers = parser.add_subparsers()
- alembic_commands = (
- cast(CommandFunction, fn)
- for fn in (getattr(command, name) for name in dir(command))
- if (
- inspect.isfunction(fn)
- and fn.__name__[0] != "_"
- and fn.__module__ == "alembic.command"
- )
- )
-
- for fn in alembic_commands:
- self.register_command(fn)
-
- self.parser = parser
-
- def register_command(self, fn: CommandFunction) -> None:
- """Registers a function as a CLI subcommand. The subcommand name
- matches the function name, the arguments are extracted from the
- signature and the help text is read from the docstring.
-
- .. versionadded:: 1.15.3
-
- .. seealso::
-
- :ref:`custom_commandline`
- """
-
- positional, kwarg, help_text = self._inspect_function(fn)
-
- subparser = self.subparsers.add_parser(fn.__name__, help=help_text)
- subparser.set_defaults(cmd=(fn, positional, kwarg))
-
- for arg in kwarg:
- if arg in self._KWARGS_OPTS:
- kwarg_opt = self._KWARGS_OPTS[arg]
- args, opts = kwarg_opt[0:-1], kwarg_opt[-1]
- subparser.add_argument(*args, **opts) # type:ignore
-
- for arg in positional:
- opts = self._POSITIONAL_OPTS.get(arg, {})
- subparser.add_argument(arg, **opts) # type:ignore
-
- def _inspect_function(self, fn: CommandFunction) -> tuple[Any, Any, str]:
- spec = compat.inspect_getfullargspec(fn)
- if spec[3] is not None:
- positional = spec[0][1 : -len(spec[3])]
- kwarg = spec[0][-len(spec[3]) :]
- else:
- positional = spec[0][1:]
- kwarg = []
-
- if fn in self._POSITIONAL_TRANSLATIONS:
- positional = [
- self._POSITIONAL_TRANSLATIONS[fn].get(name, name)
- for name in positional
- ]
-
- # parse first line(s) of helptext without a line break
- help_ = fn.__doc__
- if help_:
- help_lines = []
- for line in help_.split("\n"):
- if not line.strip():
- break
- else:
- help_lines.append(line.strip())
- else:
- help_lines = []
-
- help_text = " ".join(help_lines)
-
- return positional, kwarg, help_text
-
- def run_cmd(self, config: Config, options: Namespace) -> None:
- fn, positional, kwarg = options.cmd
-
- try:
- fn(
- config,
- *[getattr(options, k, None) for k in positional],
- **{k: getattr(options, k, None) for k in kwarg},
- )
- except util.CommandError as e:
- if options.raiseerr:
- raise
- else:
- util.err(str(e), **config.messaging_opts)
-
- def _inis_from_config(self, options: Namespace) -> tuple[str, str]:
- names = options.config
-
- alembic_config_env = os.environ.get("ALEMBIC_CONFIG")
- if (
- alembic_config_env
- and os.path.basename(alembic_config_env) == "pyproject.toml"
- ):
- default_pyproject_toml = alembic_config_env
- default_alembic_config = "alembic.ini"
- elif alembic_config_env:
- default_pyproject_toml = "pyproject.toml"
- default_alembic_config = alembic_config_env
- else:
- default_alembic_config = "alembic.ini"
- default_pyproject_toml = "pyproject.toml"
-
- if not names:
- return default_pyproject_toml, default_alembic_config
-
- toml = ini = None
-
- for name in names:
- if os.path.basename(name) == "pyproject.toml":
- if toml is not None:
- raise util.CommandError(
- "pyproject.toml indicated more than once"
- )
- toml = name
- else:
- if ini is not None:
- raise util.CommandError(
- "only one ini file may be indicated"
- )
- ini = name
-
- return toml if toml else default_pyproject_toml, (
- ini if ini else default_alembic_config
- )
-
- def main(self, argv: Optional[Sequence[str]] = None) -> None:
- """Executes the command line with the provided arguments."""
- options = self.parser.parse_args(argv)
- if not hasattr(options, "cmd"):
- # see http://bugs.python.org/issue9253, argparse
- # behavior changed incompatibly in py3.3
- self.parser.error("too few arguments")
- else:
- toml, ini = self._inis_from_config(options)
- cfg = Config(
- file_=ini,
- toml_file=toml,
- ini_section=options.name,
- cmd_opts=options,
- )
- self.run_cmd(cfg, options)
-
-
-def main(
- argv: Optional[Sequence[str]] = None,
- prog: Optional[str] = None,
- **kwargs: Any,
-) -> None:
- """The console runner function for Alembic."""
-
- CommandLine(prog=prog).main(argv=argv)
-
-
-if __name__ == "__main__":
- main()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/context.py b/backend/.venv/lib/python3.12/site-packages/alembic/context.py
deleted file mode 100644
index 758fca8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/context.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .runtime.environment import EnvironmentContext
-
-# create proxy functions for
-# each method on the EnvironmentContext class.
-EnvironmentContext.create_module_class_proxy(globals(), locals())
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/context.pyi b/backend/.venv/lib/python3.12/site-packages/alembic/context.pyi
deleted file mode 100644
index 6045d8b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/context.pyi
+++ /dev/null
@@ -1,876 +0,0 @@
-# ### this file stubs are generated by tools/write_pyi.py - do not edit ###
-# ### imports are manually managed
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import Collection
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Literal
-from typing import Mapping
-from typing import MutableMapping
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import TextIO
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from typing_extensions import ContextManager
-
-if TYPE_CHECKING:
- from sqlalchemy.engine.base import Connection
- from sqlalchemy.engine.url import URL
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import FetchedValue
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .autogenerate.api import AutogenContext
- from .config import Config
- from .operations.ops import MigrationScript
- from .runtime.migration import _ProxyTransaction
- from .runtime.migration import MigrationContext
- from .runtime.migration import MigrationInfo
- from .script import ScriptDirectory
-
-### end imports ###
-
-def begin_transaction() -> (
- Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]
-):
- """Return a context manager that will
- enclose an operation within a "transaction",
- as defined by the environment's offline
- and transactional DDL settings.
-
- e.g.::
-
- with context.begin_transaction():
- context.run_migrations()
-
- :meth:`.begin_transaction` is intended to
- "do the right thing" regardless of
- calling context:
-
- * If :meth:`.is_transactional_ddl` is ``False``,
- returns a "do nothing" context manager
- which otherwise produces no transactional
- state or directives.
- * If :meth:`.is_offline_mode` is ``True``,
- returns a context manager that will
- invoke the :meth:`.DefaultImpl.emit_begin`
- and :meth:`.DefaultImpl.emit_commit`
- methods, which will produce the string
- directives ``BEGIN`` and ``COMMIT`` on
- the output stream, as rendered by the
- target backend (e.g. SQL Server would
- emit ``BEGIN TRANSACTION``).
- * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin`
- on the current online connection, which
- returns a :class:`sqlalchemy.engine.Transaction`
- object. This object demarcates a real
- transaction and is itself a context manager,
- which will roll back if an exception
- is raised.
-
- Note that a custom ``env.py`` script which
- has more specific transactional needs can of course
- manipulate the :class:`~sqlalchemy.engine.Connection`
- directly to produce transactional state in "online"
- mode.
-
- """
-
-config: Config
-
-def configure(
- connection: Optional[Connection] = None,
- url: Union[str, URL, None] = None,
- dialect_name: Optional[str] = None,
- dialect_opts: Optional[Dict[str, Any]] = None,
- transactional_ddl: Optional[bool] = None,
- transaction_per_migration: bool = False,
- output_buffer: Optional[TextIO] = None,
- starting_rev: Optional[str] = None,
- tag: Optional[str] = None,
- template_args: Optional[Dict[str, Any]] = None,
- render_as_batch: bool = False,
- target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
- include_name: Optional[
- Callable[
- [
- Optional[str],
- Literal[
- "schema",
- "table",
- "column",
- "index",
- "unique_constraint",
- "foreign_key_constraint",
- ],
- MutableMapping[
- Literal[
- "schema_name",
- "table_name",
- "schema_qualified_table_name",
- ],
- Optional[str],
- ],
- ],
- bool,
- ]
- ] = None,
- include_object: Optional[
- Callable[
- [
- SchemaItem,
- Optional[str],
- Literal[
- "schema",
- "table",
- "column",
- "index",
- "unique_constraint",
- "foreign_key_constraint",
- ],
- bool,
- Optional[SchemaItem],
- ],
- bool,
- ]
- ] = None,
- include_schemas: bool = False,
- process_revision_directives: Optional[
- Callable[
- [
- MigrationContext,
- Union[str, Iterable[Optional[str]], Iterable[str]],
- List[MigrationScript],
- ],
- None,
- ]
- ] = None,
- compare_type: Union[
- bool,
- Callable[
- [
- MigrationContext,
- Column[Any],
- Column[Any],
- TypeEngine[Any],
- TypeEngine[Any],
- ],
- Optional[bool],
- ],
- ] = True,
- compare_server_default: Union[
- bool,
- Callable[
- [
- MigrationContext,
- Column[Any],
- Column[Any],
- Optional[str],
- Optional[FetchedValue],
- Optional[str],
- ],
- Optional[bool],
- ],
- ] = False,
- render_item: Optional[
- Callable[[str, Any, AutogenContext], Union[str, Literal[False]]]
- ] = None,
- literal_binds: bool = False,
- upgrade_token: str = "upgrades",
- downgrade_token: str = "downgrades",
- alembic_module_prefix: str = "op.",
- sqlalchemy_module_prefix: str = "sa.",
- user_module_prefix: Optional[str] = None,
- on_version_apply: Optional[
- Callable[
- [
- MigrationContext,
- MigrationInfo,
- Collection[Any],
- Mapping[str, Any],
- ],
- None,
- ]
- ] = None,
- autogenerate_plugins: Optional[Sequence[str]] = None,
- **kw: Any,
-) -> None:
- """Configure a :class:`.MigrationContext` within this
- :class:`.EnvironmentContext` which will provide database
- connectivity and other configuration to a series of
- migration scripts.
-
- Many methods on :class:`.EnvironmentContext` require that
- this method has been called in order to function, as they
- ultimately need to have database access or at least access
- to the dialect in use. Those which do are documented as such.
-
- The important thing needed by :meth:`.configure` is a
- means to determine what kind of database dialect is in use.
- An actual connection to that database is needed only if
- the :class:`.MigrationContext` is to be used in
- "online" mode.
-
- If the :meth:`.is_offline_mode` function returns ``True``,
- then no connection is needed here. Otherwise, the
- ``connection`` parameter should be present as an
- instance of :class:`sqlalchemy.engine.Connection`.
-
- This function is typically called from the ``env.py``
- script within a migration environment. It can be called
- multiple times for an invocation. The most recent
- :class:`~sqlalchemy.engine.Connection`
- for which it was called is the one that will be operated upon
- by the next call to :meth:`.run_migrations`.
-
- General parameters:
-
- :param connection: a :class:`~sqlalchemy.engine.Connection`
- to use
- for SQL execution in "online" mode. When present, is also
- used to determine the type of dialect in use.
- :param url: a string database url, or a
- :class:`sqlalchemy.engine.url.URL` object.
- The type of dialect to be used will be derived from this if
- ``connection`` is not passed.
- :param dialect_name: string name of a dialect, such as
- "postgresql", "mssql", etc.
- The type of dialect to be used will be derived from this if
- ``connection`` and ``url`` are not passed.
- :param dialect_opts: dictionary of options to be passed to dialect
- constructor.
- :param transactional_ddl: Force the usage of "transactional"
- DDL on or off;
- this otherwise defaults to whether or not the dialect in
- use supports it.
- :param transaction_per_migration: if True, nest each migration script
- in a transaction rather than the full series of migrations to
- run.
- :param output_buffer: a file-like object that will be used
- for textual output
- when the ``--sql`` option is used to generate SQL scripts.
- Defaults to
- ``sys.stdout`` if not passed here and also not present on
- the :class:`.Config`
- object. The value here overrides that of the :class:`.Config`
- object.
- :param output_encoding: when using ``--sql`` to generate SQL
- scripts, apply this encoding to the string output.
- :param literal_binds: when using ``--sql`` to generate SQL
- scripts, pass through the ``literal_binds`` flag to the compiler
- so that any literal values that would ordinarily be bound
- parameters are converted to plain strings.
-
- .. warning:: Dialects can typically only handle simple datatypes
- like strings and numbers for auto-literal generation. Datatypes
- like dates, intervals, and others may still require manual
- formatting, typically using :meth:`.Operations.inline_literal`.
-
- .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy
- versions prior to 0.8 where this feature is not supported.
-
- .. seealso::
-
- :meth:`.Operations.inline_literal`
-
- :param starting_rev: Override the "starting revision" argument
- when using ``--sql`` mode.
- :param tag: a string tag for usage by custom ``env.py`` scripts.
- Set via the ``--tag`` option, can be overridden here.
- :param template_args: dictionary of template arguments which
- will be added to the template argument environment when
- running the "revision" command. Note that the script environment
- is only run within the "revision" command if the --autogenerate
- option is used, or if the option "revision_environment=true"
- is present in the alembic.ini file.
-
- :param version_table: The name of the Alembic version table.
- The default is ``'alembic_version'``.
- :param version_table_schema: Optional schema to place version
- table within.
- :param version_table_pk: boolean, whether the Alembic version table
- should use a primary key constraint for the "value" column; this
- only takes effect when the table is first created.
- Defaults to True; setting to False should not be necessary and is
- here for backwards compatibility reasons.
- :param on_version_apply: a callable or collection of callables to be
- run for each migration step.
- The callables will be run in the order they are given, once for
- each migration step, after the respective operation has been
- applied but before its transaction is finalized.
- Each callable accepts no positional arguments and the following
- keyword arguments:
-
- * ``ctx``: the :class:`.MigrationContext` running the migration,
- * ``step``: a :class:`.MigrationInfo` representing the
- step currently being applied,
- * ``heads``: a collection of version strings representing the
- current heads,
- * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`.
-
- Parameters specific to the autogenerate feature, when
- ``alembic revision`` is run with the ``--autogenerate`` feature:
-
- :param target_metadata: a :class:`sqlalchemy.schema.MetaData`
- object, or a sequence of :class:`~sqlalchemy.schema.MetaData`
- objects, that will be consulted during autogeneration.
- The tables present in each :class:`~sqlalchemy.schema.MetaData`
- will be compared against
- what is locally available on the target
- :class:`~sqlalchemy.engine.Connection`
- to produce candidate upgrade/downgrade operations.
- :param compare_type: Indicates type comparison behavior during
- an autogenerate
- operation. Defaults to ``True`` turning on type comparison, which
- has good accuracy on most backends. See :ref:`compare_types`
- for an example as well as information on other type
- comparison options. Set to ``False`` which disables type
- comparison. A callable can also be passed to provide custom type
- comparison, see :ref:`compare_types` for additional details.
-
- .. versionchanged:: 1.12.0 The default value of
- :paramref:`.EnvironmentContext.configure.compare_type` has been
- changed to ``True``.
-
- .. seealso::
-
- :ref:`compare_types`
-
- :paramref:`.EnvironmentContext.configure.compare_server_default`
-
- :param compare_server_default: Indicates server default comparison
- behavior during
- an autogenerate operation. Defaults to ``False`` which disables
- server default
- comparison. Set to ``True`` to turn on server default comparison,
- which has
- varied accuracy depending on backend.
-
- To customize server default comparison behavior, a callable may
- be specified
- which can filter server default comparisons during an
- autogenerate operation.
- defaults during an autogenerate operation. The format of this
- callable is::
-
- def my_compare_server_default(context, inspected_column,
- metadata_column, inspected_default, metadata_default,
- rendered_metadata_default):
- # return True if the defaults are different,
- # False if not, or None to allow the default implementation
- # to compare these defaults
- return None
-
- context.configure(
- # ...
- compare_server_default = my_compare_server_default
- )
-
- ``inspected_column`` is a dictionary structure as returned by
- :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas
- ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from
- the local model environment.
-
- A return value of ``None`` indicates to allow default server default
- comparison
- to proceed. Note that some backends such as Postgresql actually
- execute
- the two defaults on the database side to compare for equivalence.
-
- .. seealso::
-
- :paramref:`.EnvironmentContext.configure.compare_type`
-
- :param include_name: A callable function which is given
- the chance to return ``True`` or ``False`` for any database reflected
- object based on its name, including database schema names when
- the :paramref:`.EnvironmentContext.configure.include_schemas` flag
- is set to ``True``.
-
- The function accepts the following positional arguments:
-
- * ``name``: the name of the object, such as schema name or table name.
- Will be ``None`` when indicating the default schema name of the
- database connection.
- * ``type``: a string describing the type of object; currently
- ``"schema"``, ``"table"``, ``"column"``, ``"index"``,
- ``"unique_constraint"``, or ``"foreign_key_constraint"``
- * ``parent_names``: a dictionary of "parent" object names, that are
- relative to the name being given. Keys in this dictionary may
- include: ``"schema_name"``, ``"table_name"`` or
- ``"schema_qualified_table_name"``.
-
- E.g.::
-
- def include_name(name, type_, parent_names):
- if type_ == "schema":
- return name in ["schema_one", "schema_two"]
- else:
- return True
-
- context.configure(
- # ...
- include_schemas = True,
- include_name = include_name
- )
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_object`
-
- :paramref:`.EnvironmentContext.configure.include_schemas`
-
-
- :param include_object: A callable function which is given
- the chance to return ``True`` or ``False`` for any object,
- indicating if the given object should be considered in the
- autogenerate sweep.
-
- The function accepts the following positional arguments:
-
- * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such
- as a :class:`~sqlalchemy.schema.Table`,
- :class:`~sqlalchemy.schema.Column`,
- :class:`~sqlalchemy.schema.Index`
- :class:`~sqlalchemy.schema.UniqueConstraint`,
- or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object
- * ``name``: the name of the object. This is typically available
- via ``object.name``.
- * ``type``: a string describing the type of object; currently
- ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``,
- or ``"foreign_key_constraint"``
- * ``reflected``: ``True`` if the given object was produced based on
- table reflection, ``False`` if it's from a local :class:`.MetaData`
- object.
- * ``compare_to``: the object being compared against, if available,
- else ``None``.
-
- E.g.::
-
- def include_object(object, name, type_, reflected, compare_to):
- if (type_ == "column" and
- not reflected and
- object.info.get("skip_autogenerate", False)):
- return False
- else:
- return True
-
- context.configure(
- # ...
- include_object = include_object
- )
-
- For the use case of omitting specific schemas from a target database
- when :paramref:`.EnvironmentContext.configure.include_schemas` is
- set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema`
- attribute can be checked for each :class:`~sqlalchemy.schema.Table`
- object passed to the hook, however it is much more efficient
- to filter on schemas before reflection of objects takes place
- using the :paramref:`.EnvironmentContext.configure.include_name`
- hook.
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_name`
-
- :paramref:`.EnvironmentContext.configure.include_schemas`
-
- :param render_as_batch: if True, commands which alter elements
- within a table will be placed under a ``with batch_alter_table():``
- directive, so that batch migrations will take place.
-
- .. seealso::
-
- :ref:`batch_migrations`
-
- :param include_schemas: If True, autogenerate will scan across
- all schemas located by the SQLAlchemy
- :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names`
- method, and include all differences in tables found across all
- those schemas. When using this option, you may want to also
- use the :paramref:`.EnvironmentContext.configure.include_name`
- parameter to specify a callable which
- can filter the tables/schemas that get included.
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_name`
-
- :paramref:`.EnvironmentContext.configure.include_object`
-
- :param render_item: Callable that can be used to override how
- any schema item, i.e. column, constraint, type,
- etc., is rendered for autogenerate. The callable receives a
- string describing the type of object, the object, and
- the autogen context. If it returns False, the
- default rendering method will be used. If it returns None,
- the item will not be rendered in the context of a Table
- construct, that is, can be used to skip columns or constraints
- within op.create_table()::
-
- def my_render_column(type_, col, autogen_context):
- if type_ == "column" and isinstance(col, MySpecialCol):
- return repr(col)
- else:
- return False
-
- context.configure(
- # ...
- render_item = my_render_column
- )
-
- Available values for the type string include: ``"column"``,
- ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``,
- ``"type"``, ``"server_default"``.
-
- .. seealso::
-
- :ref:`autogen_render_types`
-
- :param upgrade_token: When autogenerate completes, the text of the
- candidate upgrade operations will be present in this template
- variable when ``script.py.mako`` is rendered. Defaults to
- ``upgrades``.
- :param downgrade_token: When autogenerate completes, the text of the
- candidate downgrade operations will be present in this
- template variable when ``script.py.mako`` is rendered. Defaults to
- ``downgrades``.
-
- :param alembic_module_prefix: When autogenerate refers to Alembic
- :mod:`alembic.operations` constructs, this prefix will be used
- (i.e. ``op.create_table``) Defaults to "``op.``".
- Can be ``None`` to indicate no prefix.
-
- :param sqlalchemy_module_prefix: When autogenerate refers to
- SQLAlchemy
- :class:`~sqlalchemy.schema.Column` or type classes, this prefix
- will be used
- (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``".
- Can be ``None`` to indicate no prefix.
- Note that when dialect-specific types are rendered, autogenerate
- will render them using the dialect module name, i.e. ``mssql.BIT()``,
- ``postgresql.UUID()``.
-
- :param user_module_prefix: When autogenerate refers to a SQLAlchemy
- type (e.g. :class:`.TypeEngine`) where the module name is not
- under the ``sqlalchemy`` namespace, this prefix will be used
- within autogenerate. If left at its default of
- ``None``, the ``__module__`` attribute of the type is used to
- render the import module. It's a good practice to set this
- and to have all custom types be available from a fixed module space,
- in order to future-proof migration files against reorganizations
- in modules.
-
- .. seealso::
-
- :ref:`autogen_module_prefix`
-
- :param process_revision_directives: a callable function that will
- be passed a structure representing the end result of an autogenerate
- or plain "revision" operation, which can be manipulated to affect
- how the ``alembic revision`` command ultimately outputs new
- revision scripts. The structure of the callable is::
-
- def process_revision_directives(context, revision, directives):
- pass
-
- The ``directives`` parameter is a Python list containing
- a single :class:`.MigrationScript` directive, which represents
- the revision file to be generated. This list as well as its
- contents may be freely modified to produce any set of commands.
- The section :ref:`customizing_revision` shows an example of
- doing this. The ``context`` parameter is the
- :class:`.MigrationContext` in use,
- and ``revision`` is a tuple of revision identifiers representing the
- current revision of the database.
-
- The callable is invoked at all times when the ``--autogenerate``
- option is passed to ``alembic revision``. If ``--autogenerate``
- is not passed, the callable is invoked only if the
- ``revision_environment`` variable is set to True in the Alembic
- configuration, in which case the given ``directives`` collection
- will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps`
- collections for ``.upgrade_ops`` and ``.downgrade_ops``. The
- ``--autogenerate`` option itself can be inferred by inspecting
- ``context.config.cmd_opts.autogenerate``.
-
- The callable function may optionally be an instance of
- a :class:`.Rewriter` object. This is a helper object that
- assists in the production of autogenerate-stream rewriter functions.
-
- .. seealso::
-
- :ref:`customizing_revision`
-
- :ref:`autogen_rewriter`
-
- :paramref:`.command.revision.process_revision_directives`
-
- :param autogenerate_plugins: A list of string names of "plugins" that
- should participate in this autogenerate run. Defaults to the list
- ``["alembic.autogenerate.*"]``, which indicates that Alembic's default
- autogeneration plugins will be used.
-
- See the section :ref:`plugins_autogenerate` for complete background
- on how to use this parameter.
-
- .. versionadded:: 1.18.0 Added a new plugin system for autogenerate
- compare directives.
-
- .. seealso::
-
- :ref:`plugins_autogenerate` - background on enabling/disabling
- autogenerate plugins
-
- :ref:`alembic.plugins.toplevel` - Introduction and documentation
- to the plugin system
-
- Parameters specific to individual backends:
-
- :param mssql_batch_separator: The "batch separator" which will
- be placed between each statement when generating offline SQL Server
- migrations. Defaults to ``GO``. Note this is in addition to the
- customary semicolon ``;`` at the end of each statement; SQL Server
- considers the "batch separator" to denote the end of an
- individual statement execution, and cannot group certain
- dependent operations in one step.
- :param oracle_batch_separator: The "batch separator" which will
- be placed between each statement when generating offline
- Oracle migrations. Defaults to ``/``. Oracle doesn't add a
- semicolon between statements like most other backends.
-
- """
-
-def execute(
- sql: Union[Executable, str],
- execution_options: Optional[Dict[str, Any]] = None,
-) -> None:
- """Execute the given SQL using the current change context.
-
- The behavior of :meth:`.execute` is the same
- as that of :meth:`.Operations.execute`. Please see that
- function's documentation for full detail including
- caveats and limitations.
-
- This function requires that a :class:`.MigrationContext` has
- first been made available via :meth:`.configure`.
-
- """
-
-def get_bind() -> Connection:
- """Return the current 'bind'.
-
- In "online" mode, this is the
- :class:`sqlalchemy.engine.Connection` currently being used
- to emit SQL to the database.
-
- This function requires that a :class:`.MigrationContext`
- has first been made available via :meth:`.configure`.
-
- """
-
-def get_context() -> MigrationContext:
- """Return the current :class:`.MigrationContext` object.
-
- If :meth:`.EnvironmentContext.configure` has not been
- called yet, raises an exception.
-
- """
-
-def get_head_revision() -> Union[str, Tuple[str, ...], None]:
- """Return the hex identifier of the 'head' script revision.
-
- If the script directory has multiple heads, this
- method raises a :class:`.CommandError`;
- :meth:`.EnvironmentContext.get_head_revisions` should be preferred.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso:: :meth:`.EnvironmentContext.get_head_revisions`
-
- """
-
-def get_head_revisions() -> Union[str, Tuple[str, ...], None]:
- """Return the hex identifier of the 'heads' script revision(s).
-
- This returns a tuple containing the version number of all
- heads in the script directory.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
-
-def get_revision_argument() -> Union[str, Tuple[str, ...], None]:
- """Get the 'destination' revision argument.
-
- This is typically the argument passed to the
- ``upgrade`` or ``downgrade`` command.
-
- If it was specified as ``head``, the actual
- version number is returned; if specified
- as ``base``, ``None`` is returned.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
-
-def get_starting_revision_argument() -> Union[str, Tuple[str, ...], None]:
- """Return the 'starting revision' argument,
- if the revision was passed using ``start:end``.
-
- This is only meaningful in "offline" mode.
- Returns ``None`` if no value is available
- or was configured.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
-
-def get_tag_argument() -> Optional[str]:
- """Return the value passed for the ``--tag`` argument, if any.
-
- The ``--tag`` argument is not used directly by Alembic,
- but is available for custom ``env.py`` configurations that
- wish to use it; particularly for offline generation scripts
- that wish to generate tagged filenames.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso::
-
- :meth:`.EnvironmentContext.get_x_argument` - a newer and more
- open ended system of extending ``env.py`` scripts via the command
- line.
-
- """
-
-@overload
-def get_x_argument(as_dictionary: Literal[False]) -> List[str]: ...
-@overload
-def get_x_argument(as_dictionary: Literal[True]) -> Dict[str, str]: ...
-@overload
-def get_x_argument(
- as_dictionary: bool = ...,
-) -> Union[List[str], Dict[str, str]]:
- """Return the value(s) passed for the ``-x`` argument, if any.
-
- The ``-x`` argument is an open ended flag that allows any user-defined
- value or values to be passed on the command line, then available
- here for consumption by a custom ``env.py`` script.
-
- The return value is a list, returned directly from the ``argparse``
- structure. If ``as_dictionary=True`` is passed, the ``x`` arguments
- are parsed using ``key=value`` format into a dictionary that is
- then returned. If there is no ``=`` in the argument, value is an empty
- string.
-
- .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when
- arguments are passed without the ``=`` symbol.
-
- For example, to support passing a database URL on the command line,
- the standard ``env.py`` script can be modified like this::
-
- cmd_line_url = context.get_x_argument(
- as_dictionary=True).get('dbname')
- if cmd_line_url:
- engine = create_engine(cmd_line_url)
- else:
- engine = engine_from_config(
- config.get_section(config.config_ini_section),
- prefix='sqlalchemy.',
- poolclass=pool.NullPool)
-
- This then takes effect by running the ``alembic`` script as::
-
- alembic -x dbname=postgresql://user:pass@host/dbname upgrade head
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso::
-
- :meth:`.EnvironmentContext.get_tag_argument`
-
- :attr:`.Config.cmd_opts`
-
- """
-
-def is_offline_mode() -> bool:
- """Return True if the current migrations environment
- is running in "offline mode".
-
- This is ``True`` or ``False`` depending
- on the ``--sql`` flag passed.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
-
-def is_transactional_ddl() -> bool:
- """Return True if the context is configured to expect a
- transactional DDL capable backend.
-
- This defaults to the type of database in use, and
- can be overridden by the ``transactional_ddl`` argument
- to :meth:`.configure`
-
- This function requires that a :class:`.MigrationContext`
- has first been made available via :meth:`.configure`.
-
- """
-
-def run_migrations(**kw: Any) -> None:
- """Run migrations as determined by the current command line
- configuration
- as well as versioning information present (or not) in the current
- database connection (if one is present).
-
- The function accepts optional ``**kw`` arguments. If these are
- passed, they are sent directly to the ``upgrade()`` and
- ``downgrade()``
- functions within each target revision file. By modifying the
- ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()``
- functions accept arguments, parameters can be passed here so that
- contextual information, usually information to identify a particular
- database in use, can be passed from a custom ``env.py`` script
- to the migration functions.
-
- This function requires that a :class:`.MigrationContext` has
- first been made available via :meth:`.configure`.
-
- """
-
-script: ScriptDirectory
-
-def static_output(text: str) -> None:
- """Emit text directly to the "offline" SQL stream.
-
- Typically this is for emitting comments that
- start with --. The statement is not treated
- as a SQL execution, no ; or batch separator
- is added, etc.
-
- """
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py
deleted file mode 100644
index f2f72b3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from . import mssql
-from . import mysql
-from . import oracle
-from . import postgresql
-from . import sqlite
-from .impl import DefaultImpl as DefaultImpl
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 02daadb..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/_autogen.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/_autogen.cpython-312.pyc
deleted file mode 100644
index 670e242..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/_autogen.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index b098b0a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc
deleted file mode 100644
index d1efd25..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc
deleted file mode 100644
index 8f73132..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc
deleted file mode 100644
index 87c7443..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc
deleted file mode 100644
index 1cc1f5c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc
deleted file mode 100644
index dd05e24..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc
deleted file mode 100644
index 9897466..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
deleted file mode 100644
index 74715b1..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
+++ /dev/null
@@ -1,329 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-from typing import Any
-from typing import ClassVar
-from typing import Dict
-from typing import Generic
-from typing import NamedTuple
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy.sql.schema import Constraint
-from sqlalchemy.sql.schema import ForeignKeyConstraint
-from sqlalchemy.sql.schema import Index
-from sqlalchemy.sql.schema import UniqueConstraint
-from typing_extensions import TypeGuard
-
-from .. import util
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from alembic.autogenerate.api import AutogenContext
- from alembic.ddl.impl import DefaultImpl
-
-CompareConstraintType = Union[Constraint, Index]
-
-_C = TypeVar("_C", bound=CompareConstraintType)
-
-_clsreg: Dict[str, Type[_constraint_sig]] = {}
-
-
-class ComparisonResult(NamedTuple):
- status: Literal["equal", "different", "skip"]
- message: str
-
- @property
- def is_equal(self) -> bool:
- return self.status == "equal"
-
- @property
- def is_different(self) -> bool:
- return self.status == "different"
-
- @property
- def is_skip(self) -> bool:
- return self.status == "skip"
-
- @classmethod
- def Equal(cls) -> ComparisonResult:
- """the constraints are equal."""
- return cls("equal", "The two constraints are equal")
-
- @classmethod
- def Different(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
- """the constraints are different for the provided reason(s)."""
- return cls("different", ", ".join(util.to_list(reason)))
-
- @classmethod
- def Skip(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult:
- """the constraint cannot be compared for the provided reason(s).
-
- The message is logged, but the constraints will be otherwise
- considered equal, meaning that no migration command will be
- generated.
- """
- return cls("skip", ", ".join(util.to_list(reason)))
-
-
-class _constraint_sig(Generic[_C]):
- const: _C
-
- _sig: Tuple[Any, ...]
- name: Optional[sqla_compat._ConstraintNameDefined]
-
- impl: DefaultImpl
-
- _is_index: ClassVar[bool] = False
- _is_fk: ClassVar[bool] = False
- _is_uq: ClassVar[bool] = False
-
- _is_metadata: bool
-
- def __init_subclass__(cls) -> None:
- cls._register()
-
- @classmethod
- def _register(cls):
- raise NotImplementedError()
-
- def __init__(
- self, is_metadata: bool, impl: DefaultImpl, const: _C
- ) -> None:
- raise NotImplementedError()
-
- def compare_to_reflected(
- self, other: _constraint_sig[Any]
- ) -> ComparisonResult:
- assert self.impl is other.impl
- assert self._is_metadata
- assert not other._is_metadata
-
- return self._compare_to_reflected(other)
-
- def _compare_to_reflected(
- self, other: _constraint_sig[_C]
- ) -> ComparisonResult:
- raise NotImplementedError()
-
- @classmethod
- def from_constraint(
- cls, is_metadata: bool, impl: DefaultImpl, constraint: _C
- ) -> _constraint_sig[_C]:
- # these could be cached by constraint/impl, however, if the
- # constraint is modified in place, then the sig is wrong. the mysql
- # impl currently does this, and if we fixed that we can't be sure
- # someone else might do it too, so play it safe.
- sig = _clsreg[constraint.__visit_name__](is_metadata, impl, constraint)
- return sig
-
- def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
- return sqla_compat._get_constraint_final_name(
- self.const, context.dialect
- )
-
- @util.memoized_property
- def is_named(self):
- return sqla_compat._constraint_is_named(self.const, self.impl.dialect)
-
- @util.memoized_property
- def unnamed(self) -> Tuple[Any, ...]:
- return self._sig
-
- @util.memoized_property
- def unnamed_no_options(self) -> Tuple[Any, ...]:
- raise NotImplementedError()
-
- @util.memoized_property
- def _full_sig(self) -> Tuple[Any, ...]:
- return (self.name,) + self.unnamed
-
- def __eq__(self, other) -> bool:
- return self._full_sig == other._full_sig
-
- def __ne__(self, other) -> bool:
- return self._full_sig != other._full_sig
-
- def __hash__(self) -> int:
- return hash(self._full_sig)
-
-
-class _uq_constraint_sig(_constraint_sig[UniqueConstraint]):
- _is_uq = True
-
- @classmethod
- def _register(cls) -> None:
- _clsreg["unique_constraint"] = cls
-
- is_unique = True
-
- def __init__(
- self,
- is_metadata: bool,
- impl: DefaultImpl,
- const: UniqueConstraint,
- ) -> None:
- self.impl = impl
- self.const = const
- self.name = sqla_compat.constraint_name_or_none(const.name)
- self._sig = tuple(sorted([col.name for col in const.columns]))
- self._is_metadata = is_metadata
-
- @property
- def column_names(self) -> Tuple[str, ...]:
- return tuple([col.name for col in self.const.columns])
-
- def _compare_to_reflected(
- self, other: _constraint_sig[_C]
- ) -> ComparisonResult:
- assert self._is_metadata
- metadata_obj = self
- conn_obj = other
-
- assert is_uq_sig(conn_obj)
- return self.impl.compare_unique_constraint(
- metadata_obj.const, conn_obj.const
- )
-
-
-class _ix_constraint_sig(_constraint_sig[Index]):
- _is_index = True
-
- name: sqla_compat._ConstraintName
-
- @classmethod
- def _register(cls) -> None:
- _clsreg["index"] = cls
-
- def __init__(
- self, is_metadata: bool, impl: DefaultImpl, const: Index
- ) -> None:
- self.impl = impl
- self.const = const
- self.name = const.name
- self.is_unique = bool(const.unique)
- self._is_metadata = is_metadata
-
- def _compare_to_reflected(
- self, other: _constraint_sig[_C]
- ) -> ComparisonResult:
- assert self._is_metadata
- metadata_obj = self
- conn_obj = other
-
- assert is_index_sig(conn_obj)
- return self.impl.compare_indexes(metadata_obj.const, conn_obj.const)
-
- @util.memoized_property
- def has_expressions(self):
- return sqla_compat.is_expression_index(self.const)
-
- @util.memoized_property
- def column_names(self) -> Tuple[str, ...]:
- return tuple([col.name for col in self.const.columns])
-
- @util.memoized_property
- def column_names_optional(self) -> Tuple[Optional[str], ...]:
- return tuple(
- [getattr(col, "name", None) for col in self.const.expressions]
- )
-
- @util.memoized_property
- def is_named(self):
- return True
-
- @util.memoized_property
- def unnamed(self):
- return (self.is_unique,) + self.column_names_optional
-
-
-class _fk_constraint_sig(_constraint_sig[ForeignKeyConstraint]):
- _is_fk = True
-
- @classmethod
- def _register(cls) -> None:
- _clsreg["foreign_key_constraint"] = cls
-
- def __init__(
- self,
- is_metadata: bool,
- impl: DefaultImpl,
- const: ForeignKeyConstraint,
- ) -> None:
- self._is_metadata = is_metadata
-
- self.impl = impl
- self.const = const
-
- self.name = sqla_compat.constraint_name_or_none(const.name)
-
- (
- self.source_schema,
- self.source_table,
- self.source_columns,
- self.target_schema,
- self.target_table,
- self.target_columns,
- onupdate,
- ondelete,
- deferrable,
- initially,
- ) = sqla_compat._fk_spec(const)
-
- self._sig: Tuple[Any, ...] = (
- self.source_schema,
- self.source_table,
- tuple(self.source_columns),
- self.target_schema,
- self.target_table,
- tuple(self.target_columns),
- ) + (
- (
- (None if onupdate.lower() == "no action" else onupdate.lower())
- if onupdate
- else None
- ),
- (
- (None if ondelete.lower() == "no action" else ondelete.lower())
- if ondelete
- else None
- ),
- # convert initially + deferrable into one three-state value
- (
- "initially_deferrable"
- if initially and initially.lower() == "deferred"
- else "deferrable" if deferrable else "not deferrable"
- ),
- )
-
- @util.memoized_property
- def unnamed_no_options(self):
- return (
- self.source_schema,
- self.source_table,
- tuple(self.source_columns),
- self.target_schema,
- self.target_table,
- tuple(self.target_columns),
- )
-
-
-def is_index_sig(sig: _constraint_sig) -> TypeGuard[_ix_constraint_sig]:
- return sig._is_index
-
-
-def is_uq_sig(sig: _constraint_sig) -> TypeGuard[_uq_constraint_sig]:
- return sig._is_uq
-
-
-def is_fk_sig(sig: _constraint_sig) -> TypeGuard[_fk_constraint_sig]:
- return sig._is_fk
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/base.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/base.py
deleted file mode 100644
index caab909..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/base.py
+++ /dev/null
@@ -1,402 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import functools
-from typing import Any
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import exc
-from sqlalchemy import Integer
-from sqlalchemy import types as sqltypes
-from sqlalchemy.ext.compiler import compiles
-from sqlalchemy.schema import Column
-from sqlalchemy.schema import DDLElement
-from sqlalchemy.sql.elements import ColumnElement
-from sqlalchemy.sql.elements import quoted_name
-from sqlalchemy.sql.elements import TextClause
-from sqlalchemy.sql.schema import FetchedValue
-
-from ..util.sqla_compat import _columns_for_constraint # noqa
-from ..util.sqla_compat import _find_columns # noqa
-from ..util.sqla_compat import _fk_spec # noqa
-from ..util.sqla_compat import _is_type_bound # noqa
-from ..util.sqla_compat import _table_for_constraint # noqa
-
-if TYPE_CHECKING:
-
- from sqlalchemy import Computed
- from sqlalchemy import Identity
- from sqlalchemy.sql.compiler import Compiled
- from sqlalchemy.sql.compiler import DDLCompiler
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .impl import DefaultImpl
-
-_ServerDefaultType = Union[FetchedValue, str, TextClause, ColumnElement[Any]]
-
-
-class AlterTable(DDLElement):
- """Represent an ALTER TABLE statement.
-
- Only the string name and optional schema name of the table
- is required, not a full Table object.
-
- """
-
- def __init__(
- self,
- table_name: str,
- schema: Optional[Union[quoted_name, str]] = None,
- ) -> None:
- self.table_name = table_name
- self.schema = schema
-
-
-class RenameTable(AlterTable):
- def __init__(
- self,
- old_table_name: str,
- new_table_name: Union[quoted_name, str],
- schema: Optional[Union[quoted_name, str]] = None,
- ) -> None:
- super().__init__(old_table_name, schema=schema)
- self.new_table_name = new_table_name
-
-
-class AlterColumn(AlterTable):
- def __init__(
- self,
- name: str,
- column_name: str,
- schema: Optional[str] = None,
- existing_type: Optional[TypeEngine] = None,
- existing_nullable: Optional[bool] = None,
- existing_server_default: Optional[_ServerDefaultType] = None,
- existing_comment: Optional[str] = None,
- ) -> None:
- super().__init__(name, schema=schema)
- self.column_name = column_name
- self.existing_type = (
- sqltypes.to_instance(existing_type)
- if existing_type is not None
- else None
- )
- self.existing_nullable = existing_nullable
- self.existing_server_default = existing_server_default
- self.existing_comment = existing_comment
-
-
-class ColumnNullable(AlterColumn):
- def __init__(
- self, name: str, column_name: str, nullable: bool, **kw
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.nullable = nullable
-
-
-class ColumnType(AlterColumn):
- def __init__(
- self, name: str, column_name: str, type_: TypeEngine, **kw
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.type_ = sqltypes.to_instance(type_)
-
-
-class ColumnName(AlterColumn):
- def __init__(
- self, name: str, column_name: str, newname: str, **kw
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.newname = newname
-
-
-class ColumnDefault(AlterColumn):
- def __init__(
- self,
- name: str,
- column_name: str,
- default: Optional[_ServerDefaultType],
- **kw,
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.default = default
-
-
-class ComputedColumnDefault(AlterColumn):
- def __init__(
- self, name: str, column_name: str, default: Optional[Computed], **kw
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.default = default
-
-
-class IdentityColumnDefault(AlterColumn):
- def __init__(
- self,
- name: str,
- column_name: str,
- default: Optional[Identity],
- impl: DefaultImpl,
- **kw,
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.default = default
- self.impl = impl
-
-
-class AddColumn(AlterTable):
- def __init__(
- self,
- name: str,
- column: Column[Any],
- schema: Optional[Union[quoted_name, str]] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- super().__init__(name, schema=schema)
- self.column = column
- self.if_not_exists = if_not_exists
- self.inline_references = inline_references
-
-
-class DropColumn(AlterTable):
- def __init__(
- self,
- name: str,
- column: Column[Any],
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- ) -> None:
- super().__init__(name, schema=schema)
- self.column = column
- self.if_exists = if_exists
-
-
-class ColumnComment(AlterColumn):
- def __init__(
- self, name: str, column_name: str, comment: Optional[str], **kw
- ) -> None:
- super().__init__(name, column_name, **kw)
- self.comment = comment
-
-
-@compiles(RenameTable)
-def visit_rename_table(
- element: RenameTable, compiler: DDLCompiler, **kw
-) -> str:
- return "%s RENAME TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_table_name(compiler, element.new_table_name, element.schema),
- )
-
-
-@compiles(AddColumn)
-def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
- return "%s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- add_column(
- compiler,
- element.column,
- if_not_exists=element.if_not_exists,
- inline_references=element.inline_references,
- **kw,
- ),
- )
-
-
-@compiles(DropColumn)
-def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
- return "%s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- drop_column(
- compiler, element.column.name, if_exists=element.if_exists, **kw
- ),
- )
-
-
-@compiles(ColumnNullable)
-def visit_column_nullable(
- element: ColumnNullable, compiler: DDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- "DROP NOT NULL" if element.nullable else "SET NOT NULL",
- )
-
-
-@compiles(ColumnType)
-def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- "TYPE %s" % format_type(compiler, element.type_),
- )
-
-
-@compiles(ColumnName)
-def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
- return "%s RENAME %s TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- format_column_name(compiler, element.newname),
- )
-
-
-@compiles(ColumnDefault)
-def visit_column_default(
- element: ColumnDefault, compiler: DDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- (
- "SET DEFAULT %s" % format_server_default(compiler, element.default)
- if element.default is not None
- else "DROP DEFAULT"
- ),
- )
-
-
-@compiles(ComputedColumnDefault)
-def visit_computed_column(
- element: ComputedColumnDefault, compiler: DDLCompiler, **kw
-):
- raise exc.CompileError(
- 'Adding or removing a "computed" construct, e.g. GENERATED '
- "ALWAYS AS, to or from an existing column is not supported."
- )
-
-
-@compiles(IdentityColumnDefault)
-def visit_identity_column(
- element: IdentityColumnDefault, compiler: DDLCompiler, **kw
-):
- raise exc.CompileError(
- 'Adding, removing or modifying an "identity" construct, '
- "e.g. GENERATED AS IDENTITY, to or from an existing "
- "column is not supported in this dialect."
- )
-
-
-def quote_dotted(
- name: Union[quoted_name, str], quote: functools.partial
-) -> Union[quoted_name, str]:
- """quote the elements of a dotted name"""
-
- if isinstance(name, quoted_name):
- return quote(name)
- result = ".".join([quote(x) for x in name.split(".")])
- return result
-
-
-def format_table_name(
- compiler: Compiled,
- name: Union[quoted_name, str],
- schema: Optional[Union[quoted_name, str]],
-) -> Union[quoted_name, str]:
- quote = functools.partial(compiler.preparer.quote)
- if schema:
- return quote_dotted(schema, quote) + "." + quote(name)
- else:
- return quote(name)
-
-
-def format_column_name(
- compiler: DDLCompiler, name: Optional[Union[quoted_name, str]]
-) -> Union[quoted_name, str]:
- return compiler.preparer.quote(name) # type: ignore[arg-type]
-
-
-def format_server_default(
- compiler: DDLCompiler,
- default: Optional[_ServerDefaultType],
-) -> str:
- # this can be updated to use compiler.render_default_string
- # for SQLAlchemy 2.0 and above; not in 1.4
- default_str = compiler.get_column_default_string(
- Column("x", Integer, server_default=default)
- )
- assert default_str is not None
- return default_str
-
-
-def format_type(compiler: DDLCompiler, type_: TypeEngine) -> str:
- return compiler.dialect.type_compiler.process(type_)
-
-
-def alter_table(
- compiler: DDLCompiler,
- name: str,
- schema: Optional[str],
-) -> str:
- return "ALTER TABLE %s" % format_table_name(compiler, name, schema)
-
-
-def drop_column(
- compiler: DDLCompiler, name: str, if_exists: Optional[bool] = None, **kw
-) -> str:
- return "DROP COLUMN %s%s" % (
- "IF EXISTS " if if_exists else "",
- format_column_name(compiler, name),
- )
-
-
-def alter_column(compiler: DDLCompiler, name: str) -> str:
- return "ALTER COLUMN %s" % format_column_name(compiler, name)
-
-
-def add_column(
- compiler: DDLCompiler,
- column: Column[Any],
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- **kw,
-) -> str:
- text = "ADD COLUMN %s%s" % (
- "IF NOT EXISTS " if if_not_exists else "",
- compiler.get_column_specification(column, **kw),
- )
-
- if column.primary_key:
- text += " PRIMARY KEY"
-
- # Handle inline REFERENCES if requested
- # Only render inline if there's exactly one foreign key AND the
- # ForeignKeyConstraint is single-column, to avoid non-deterministic
- # behavior with sets and to ensure proper syntax
- if (
- inline_references
- and len(column.foreign_keys) == 1
- and (fk := list(column.foreign_keys)[0])
- and fk.constraint is not None
- and len(fk.constraint.columns) == 1
- ):
- ref_col = fk.column
- ref_table = ref_col.table
-
- # Format with proper quoting
- if ref_table.schema:
- table_name = "%s.%s" % (
- compiler.preparer.quote_schema(ref_table.schema),
- compiler.preparer.quote(ref_table.name),
- )
- else:
- table_name = compiler.preparer.quote(ref_table.name)
-
- text += " REFERENCES %s (%s)" % (
- table_name,
- compiler.preparer.quote(ref_col.name),
- )
-
- const = " ".join(
- compiler.process(constraint) for constraint in column.constraints
- )
- if const:
- text += " " + const
-
- return text
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py
deleted file mode 100644
index a615310..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/impl.py
+++ /dev/null
@@ -1,919 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import logging
-import re
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import NamedTuple
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import cast
-from sqlalchemy import Column
-from sqlalchemy import MetaData
-from sqlalchemy import PrimaryKeyConstraint
-from sqlalchemy import schema
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy import text
-
-from . import _autogen
-from . import base
-from ._autogen import _constraint_sig as _constraint_sig
-from ._autogen import ComparisonResult as ComparisonResult
-from .. import util
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from typing import Literal
- from typing import TextIO
-
- from sqlalchemy.engine import Connection
- from sqlalchemy.engine import Dialect
- from sqlalchemy.engine.cursor import CursorResult
- from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint
- from sqlalchemy.engine.interfaces import ReflectedIndex
- from sqlalchemy.engine.interfaces import ReflectedPrimaryKeyConstraint
- from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint
- from sqlalchemy.engine.reflection import Inspector
- from sqlalchemy.sql import ClauseElement
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.schema import ForeignKeyConstraint
- from sqlalchemy.sql.schema import Index
- from sqlalchemy.sql.schema import UniqueConstraint
- from sqlalchemy.sql.selectable import TableClause
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .base import _ServerDefaultType
- from ..autogenerate.api import AutogenContext
- from ..operations.batch import ApplyBatchImpl
- from ..operations.batch import BatchOperationsImpl
-
- _ReflectedConstraint = (
- ReflectedForeignKeyConstraint
- | ReflectedPrimaryKeyConstraint
- | ReflectedIndex
- | ReflectedUniqueConstraint
- )
-log = logging.getLogger(__name__)
-
-
-class ImplMeta(type):
- def __init__(
- cls,
- classname: str,
- bases: Tuple[Type[DefaultImpl]],
- dict_: Dict[str, Any],
- ):
- newtype = type.__init__(cls, classname, bases, dict_)
- if "__dialect__" in dict_:
- _impls[dict_["__dialect__"]] = cls # type: ignore[assignment]
- return newtype
-
-
-_impls: Dict[str, Type[DefaultImpl]] = {}
-
-
-class DefaultImpl(metaclass=ImplMeta):
- """Provide the entrypoint for major migration operations,
- including database-specific behavioral variances.
-
- While individual SQL/DDL constructs already provide
- for database-specific implementations, variances here
- allow for entirely different sequences of operations
- to take place for a particular migration, such as
- SQL Server's special 'IDENTITY INSERT' step for
- bulk inserts.
-
- """
-
- __dialect__ = "default"
-
- transactional_ddl = False
- command_terminator = ";"
- type_synonyms: Tuple[Set[str], ...] = ({"NUMERIC", "DECIMAL"},)
- type_arg_extract: Sequence[str] = ()
- # These attributes are deprecated in SQLAlchemy via #10247. They need to
- # be ignored to support older version that did not use dialect kwargs.
- # They only apply to Oracle and are replaced by oracle_order,
- # oracle_on_null
- identity_attrs_ignore: Tuple[str, ...] = ("order", "on_null")
-
- def __init__(
- self,
- dialect: Dialect,
- connection: Optional[Connection],
- as_sql: bool,
- transactional_ddl: Optional[bool],
- output_buffer: Optional[TextIO],
- context_opts: Dict[str, Any],
- ) -> None:
- self.dialect = dialect
- self.connection = connection
- self.as_sql = as_sql
- self.literal_binds = context_opts.get("literal_binds", False)
-
- self.output_buffer = output_buffer
- self.memo: dict = {}
- self.context_opts = context_opts
- if transactional_ddl is not None:
- self.transactional_ddl = transactional_ddl
-
- if self.literal_binds:
- if not self.as_sql:
- raise util.CommandError(
- "Can't use literal_binds setting without as_sql mode"
- )
-
- @classmethod
- def get_by_dialect(cls, dialect: Dialect) -> Type[DefaultImpl]:
- return _impls[dialect.name]
-
- def static_output(self, text: str) -> None:
- assert self.output_buffer is not None
- self.output_buffer.write(text + "\n\n")
- self.output_buffer.flush()
-
- def version_table_impl(
- self,
- *,
- version_table: str,
- version_table_schema: Optional[str],
- version_table_pk: bool,
- **kw: Any,
- ) -> Table:
- """Generate a :class:`.Table` object which will be used as the
- structure for the Alembic version table.
-
- Third party dialects may override this hook to provide an alternate
- structure for this :class:`.Table`; requirements are only that it
- be named based on the ``version_table`` parameter and contains
- at least a single string-holding column named ``version_num``.
-
- .. versionadded:: 1.14
-
- """
- vt = Table(
- version_table,
- MetaData(),
- Column("version_num", String(32), nullable=False),
- schema=version_table_schema,
- )
- if version_table_pk:
- vt.append_constraint(
- PrimaryKeyConstraint(
- "version_num", name=f"{version_table}_pkc"
- )
- )
-
- return vt
-
- def requires_recreate_in_batch(
- self, batch_op: BatchOperationsImpl
- ) -> bool:
- """Return True if the given :class:`.BatchOperationsImpl`
- would need the table to be recreated and copied in order to
- proceed.
-
- Normally, only returns True on SQLite when operations other
- than add_column are present.
-
- """
- return False
-
- def prep_table_for_batch(
- self, batch_impl: ApplyBatchImpl, table: Table
- ) -> None:
- """perform any operations needed on a table before a new
- one is created to replace it in batch mode.
-
- the PG dialect uses this to drop constraints on the table
- before the new one uses those same names.
-
- """
-
- @property
- def bind(self) -> Optional[Connection]:
- return self.connection
-
- def _exec(
- self,
- construct: Union[Executable, str],
- execution_options: Optional[Mapping[str, Any]] = None,
- multiparams: Optional[Sequence[Mapping[str, Any]]] = None,
- params: Mapping[str, Any] = util.immutabledict(),
- ) -> Optional[CursorResult]:
- if isinstance(construct, str):
- construct = text(construct)
- if self.as_sql:
- if multiparams is not None or params:
- raise TypeError("SQL parameters not allowed with as_sql")
-
- compile_kw: dict[str, Any]
- if self.literal_binds and not isinstance(
- construct, schema.DDLElement
- ):
- compile_kw = dict(compile_kwargs={"literal_binds": True})
- else:
- compile_kw = {}
-
- if TYPE_CHECKING:
- assert isinstance(construct, ClauseElement)
- compiled = construct.compile(dialect=self.dialect, **compile_kw)
- self.static_output(
- str(compiled).replace("\t", " ").strip()
- + self.command_terminator
- )
- return None
- else:
- conn = self.connection
- assert conn is not None
- if execution_options:
- conn = conn.execution_options(**execution_options)
-
- if params and multiparams is not None:
- raise TypeError(
- "Can't send params and multiparams at the same time"
- )
-
- if multiparams:
- return conn.execute(construct, multiparams)
- else:
- return conn.execute(construct, params)
-
- def execute(
- self,
- sql: Union[Executable, str],
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- self._exec(sql, execution_options)
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = False,
- name: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- schema: Optional[str] = None,
- autoincrement: Optional[bool] = None,
- comment: Optional[Union[str, Literal[False]]] = False,
- existing_comment: Optional[str] = None,
- existing_type: Optional[TypeEngine] = None,
- existing_server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = None,
- existing_nullable: Optional[bool] = None,
- existing_autoincrement: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- if autoincrement is not None or existing_autoincrement is not None:
- util.warn(
- "autoincrement and existing_autoincrement "
- "only make sense for MySQL",
- stacklevel=3,
- )
- if nullable is not None:
- self._exec(
- base.ColumnNullable(
- table_name,
- column_name,
- nullable,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- )
- )
- if server_default is not False:
- kw = {}
- cls_: Type[
- Union[
- base.ComputedColumnDefault,
- base.IdentityColumnDefault,
- base.ColumnDefault,
- ]
- ]
- if sqla_compat._server_default_is_computed(
- server_default, existing_server_default
- ):
- cls_ = base.ComputedColumnDefault
- elif sqla_compat._server_default_is_identity(
- server_default, existing_server_default
- ):
- cls_ = base.IdentityColumnDefault
- kw["impl"] = self
- else:
- cls_ = base.ColumnDefault
- self._exec(
- cls_(
- table_name,
- column_name,
- server_default, # type:ignore[arg-type]
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- **kw,
- )
- )
- if type_ is not None:
- self._exec(
- base.ColumnType(
- table_name,
- column_name,
- type_,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- )
- )
-
- if comment is not False:
- self._exec(
- base.ColumnComment(
- table_name,
- column_name,
- comment,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- )
- )
-
- # do the new name last ;)
- if name is not None:
- self._exec(
- base.ColumnName(
- table_name,
- column_name,
- name,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- )
- )
-
- def add_column(
- self,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[Union[str, quoted_name]] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- self._exec(
- base.AddColumn(
- table_name,
- column,
- schema=schema,
- if_not_exists=if_not_exists,
- inline_references=inline_references,
- )
- )
-
- def drop_column(
- self,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw,
- ) -> None:
- self._exec(
- base.DropColumn(
- table_name, column, schema=schema, if_exists=if_exists
- )
- )
-
- def add_constraint(self, const: Any, **kw: Any) -> None:
- if const._create_rule is None or const._create_rule(self):
- if sqla_compat.sqla_2_1:
- # this should be the default already
- kw.setdefault("isolate_from_table", True)
- self._exec(schema.AddConstraint(const, **kw))
-
- def drop_constraint(self, const: Constraint, **kw: Any) -> None:
- self._exec(schema.DropConstraint(const, **kw))
-
- def rename_table(
- self,
- old_table_name: str,
- new_table_name: Union[str, quoted_name],
- schema: Optional[Union[str, quoted_name]] = None,
- ) -> None:
- self._exec(
- base.RenameTable(old_table_name, new_table_name, schema=schema)
- )
-
- def create_table(self, table: Table, **kw: Any) -> None:
- table.dispatch.before_create(
- table, self.connection, checkfirst=False, _ddl_runner=self
- )
- self._exec(schema.CreateTable(table, **kw))
- table.dispatch.after_create(
- table, self.connection, checkfirst=False, _ddl_runner=self
- )
- for index in table.indexes:
- self._exec(schema.CreateIndex(index))
-
- with_comment = (
- self.dialect.supports_comments and not self.dialect.inline_comments
- )
- comment = table.comment
- if comment and with_comment:
- self.create_table_comment(table)
-
- for column in table.columns:
- comment = column.comment
- if comment and with_comment:
- self.create_column_comment(column)
-
- def drop_table(self, table: Table, **kw: Any) -> None:
- table.dispatch.before_drop(
- table, self.connection, checkfirst=False, _ddl_runner=self
- )
- self._exec(schema.DropTable(table, **kw))
- table.dispatch.after_drop(
- table, self.connection, checkfirst=False, _ddl_runner=self
- )
-
- def create_index(self, index: Index, **kw: Any) -> None:
- self._exec(schema.CreateIndex(index, **kw))
-
- def create_table_comment(self, table: Table) -> None:
- self._exec(schema.SetTableComment(table))
-
- def drop_table_comment(self, table: Table) -> None:
- self._exec(schema.DropTableComment(table))
-
- def create_column_comment(self, column: Column[Any]) -> None:
- self._exec(schema.SetColumnComment(column))
-
- def drop_index(self, index: Index, **kw: Any) -> None:
- self._exec(schema.DropIndex(index, **kw))
-
- def bulk_insert(
- self,
- table: Union[TableClause, Table],
- rows: List[dict],
- multiinsert: bool = True,
- ) -> None:
- if not isinstance(rows, list):
- raise TypeError("List expected")
- elif rows and not isinstance(rows[0], dict):
- raise TypeError("List of dictionaries expected")
- if self.as_sql:
- for row in rows:
- self._exec(
- table.insert()
- .inline()
- .values(
- **{
- k: (
- sqla_compat._literal_bindparam(
- k, v, type_=table.c[k].type
- )
- if not isinstance(
- v, sqla_compat._literal_bindparam
- )
- else v
- )
- for k, v in row.items()
- }
- )
- )
- else:
- if rows:
- if multiinsert:
- self._exec(table.insert().inline(), multiparams=rows)
- else:
- for row in rows:
- self._exec(table.insert().inline().values(**row))
-
- def _tokenize_column_type(self, column: Column) -> Params:
- definition: str
- definition = self.dialect.type_compiler.process(column.type).lower()
-
- # tokenize the SQLAlchemy-generated version of a type, so that
- # the two can be compared.
- #
- # examples:
- # NUMERIC(10, 5)
- # TIMESTAMP WITH TIMEZONE
- # INTEGER UNSIGNED
- # INTEGER (10) UNSIGNED
- # INTEGER(10) UNSIGNED
- # varchar character set utf8
- #
-
- tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition)
-
- term_tokens: List[str] = []
- paren_term = None
-
- for token in tokens:
- if re.match(r"^\(.*\)$", token):
- paren_term = token
- else:
- term_tokens.append(token)
-
- params = Params(term_tokens[0], term_tokens[1:], [], {})
-
- if paren_term:
- term: str
- for term in re.findall("[^(),]+", paren_term):
- if "=" in term:
- key, val = term.split("=")
- params.kwargs[key.strip()] = val.strip()
- else:
- params.args.append(term.strip())
-
- return params
-
- def _column_types_match(
- self, inspector_params: Params, metadata_params: Params
- ) -> bool:
- if inspector_params.token0 == metadata_params.token0:
- return True
-
- synonyms = [{t.lower() for t in batch} for batch in self.type_synonyms]
- inspector_all_terms = " ".join(
- [inspector_params.token0] + inspector_params.tokens
- )
- metadata_all_terms = " ".join(
- [metadata_params.token0] + metadata_params.tokens
- )
-
- for batch in synonyms:
- if {inspector_all_terms, metadata_all_terms}.issubset(batch) or {
- inspector_params.token0,
- metadata_params.token0,
- }.issubset(batch):
- return True
- return False
-
- def _column_args_match(
- self, inspected_params: Params, meta_params: Params
- ) -> bool:
- """We want to compare column parameters. However, we only want
- to compare parameters that are set. If they both have `collation`,
- we want to make sure they are the same. However, if only one
- specifies it, dont flag it for being less specific
- """
-
- if (
- len(meta_params.tokens) == len(inspected_params.tokens)
- and meta_params.tokens != inspected_params.tokens
- ):
- return False
-
- if (
- len(meta_params.args) == len(inspected_params.args)
- and meta_params.args != inspected_params.args
- ):
- return False
-
- insp = " ".join(inspected_params.tokens).lower()
- meta = " ".join(meta_params.tokens).lower()
-
- for reg in self.type_arg_extract:
- mi = re.search(reg, insp)
- mm = re.search(reg, meta)
-
- if mi and mm and mi.group(1) != mm.group(1):
- return False
-
- return True
-
- def compare_type(
- self, inspector_column: Column[Any], metadata_column: Column
- ) -> bool:
- """Returns True if there ARE differences between the types of the two
- columns. Takes impl.type_synonyms into account between retrospected
- and metadata types
- """
- inspector_params = self._tokenize_column_type(inspector_column)
- metadata_params = self._tokenize_column_type(metadata_column)
-
- if not self._column_types_match(inspector_params, metadata_params):
- return True
- if not self._column_args_match(inspector_params, metadata_params):
- return True
- return False
-
- def compare_server_default(
- self,
- inspector_column,
- metadata_column,
- rendered_metadata_default,
- rendered_inspector_default,
- ):
- return rendered_inspector_default != rendered_metadata_default
-
- def correct_for_autogen_constraints(
- self,
- conn_uniques: Set[UniqueConstraint],
- conn_indexes: Set[Index],
- metadata_unique_constraints: Set[UniqueConstraint],
- metadata_indexes: Set[Index],
- ) -> None:
- pass
-
- def cast_for_batch_migrate(self, existing, existing_transfer, new_type):
- if existing.type._type_affinity is not new_type._type_affinity:
- existing_transfer["expr"] = cast(
- existing_transfer["expr"], new_type
- )
-
- def render_ddl_sql_expr(
- self, expr: ClauseElement, is_server_default: bool = False, **kw: Any
- ) -> str:
- """Render a SQL expression that is typically a server default,
- index expression, etc.
-
- """
-
- compile_kw = {"literal_binds": True, "include_table": False}
-
- return str(
- expr.compile(dialect=self.dialect, compile_kwargs=compile_kw)
- )
-
- def _compat_autogen_column_reflect(self, inspector: Inspector) -> Callable:
- return self.autogen_column_reflect
-
- def correct_for_autogen_foreignkeys(
- self,
- conn_fks: Set[ForeignKeyConstraint],
- metadata_fks: Set[ForeignKeyConstraint],
- ) -> None:
- pass
-
- def autogen_column_reflect(self, inspector, table, column_info):
- """A hook that is attached to the 'column_reflect' event for when
- a Table is reflected from the database during the autogenerate
- process.
-
- Dialects can elect to modify the information gathered here.
-
- """
-
- def start_migrations(self) -> None:
- """A hook called when :meth:`.EnvironmentContext.run_migrations`
- is called.
-
- Implementations can set up per-migration-run state here.
-
- """
-
- def emit_begin(self) -> None:
- """Emit the string ``BEGIN``, or the backend-specific
- equivalent, on the current connection context.
-
- This is used in offline mode and typically
- via :meth:`.EnvironmentContext.begin_transaction`.
-
- """
- self.static_output("BEGIN" + self.command_terminator)
-
- def emit_commit(self) -> None:
- """Emit the string ``COMMIT``, or the backend-specific
- equivalent, on the current connection context.
-
- This is used in offline mode and typically
- via :meth:`.EnvironmentContext.begin_transaction`.
-
- """
- self.static_output("COMMIT" + self.command_terminator)
-
- def render_type(
- self, type_obj: TypeEngine, autogen_context: AutogenContext
- ) -> Union[str, Literal[False]]:
- return False
-
- def _compare_identity_default(self, metadata_identity, inspector_identity):
- # ignored contains the attributes that were not considered
- # because assumed to their default values in the db.
- diff, ignored = _compare_identity_options(
- metadata_identity,
- inspector_identity,
- schema.Identity(),
- skip={"always"},
- )
-
- meta_always = getattr(metadata_identity, "always", None)
- inspector_always = getattr(inspector_identity, "always", None)
- # None and False are the same in this comparison
- if bool(meta_always) != bool(inspector_always):
- diff.add("always")
-
- diff.difference_update(self.identity_attrs_ignore)
-
- # returns 3 values:
- return (
- # different identity attributes
- diff,
- # ignored identity attributes
- ignored,
- # if the two identity should be considered different
- bool(diff) or bool(metadata_identity) != bool(inspector_identity),
- )
-
- def _compare_index_unique(
- self, metadata_index: Index, reflected_index: Index
- ) -> Optional[str]:
- conn_unique = bool(reflected_index.unique)
- meta_unique = bool(metadata_index.unique)
- if conn_unique != meta_unique:
- return f"unique={conn_unique} to unique={meta_unique}"
- else:
- return None
-
- def _create_metadata_constraint_sig(
- self, constraint: _autogen._C, **opts: Any
- ) -> _constraint_sig[_autogen._C]:
- return _constraint_sig.from_constraint(True, self, constraint, **opts)
-
- def _create_reflected_constraint_sig(
- self, constraint: _autogen._C, **opts: Any
- ) -> _constraint_sig[_autogen._C]:
- return _constraint_sig.from_constraint(False, self, constraint, **opts)
-
- def compare_indexes(
- self,
- metadata_index: Index,
- reflected_index: Index,
- ) -> ComparisonResult:
- """Compare two indexes by comparing the signature generated by
- ``create_index_sig``.
-
- This method returns a ``ComparisonResult``.
- """
- msg: List[str] = []
- unique_msg = self._compare_index_unique(
- metadata_index, reflected_index
- )
- if unique_msg:
- msg.append(unique_msg)
- m_sig = self._create_metadata_constraint_sig(metadata_index)
- r_sig = self._create_reflected_constraint_sig(reflected_index)
-
- assert _autogen.is_index_sig(m_sig)
- assert _autogen.is_index_sig(r_sig)
-
- # The assumption is that the index have no expression
- for sig in m_sig, r_sig:
- if sig.has_expressions:
- log.warning(
- "Generating approximate signature for index %s. "
- "The dialect "
- "implementation should either skip expression indexes "
- "or provide a custom implementation.",
- sig.const,
- )
-
- if m_sig.column_names != r_sig.column_names:
- msg.append(
- f"expression {r_sig.column_names} to {m_sig.column_names}"
- )
-
- if msg:
- return ComparisonResult.Different(msg)
- else:
- return ComparisonResult.Equal()
-
- def compare_unique_constraint(
- self,
- metadata_constraint: UniqueConstraint,
- reflected_constraint: UniqueConstraint,
- ) -> ComparisonResult:
- """Compare two unique constraints by comparing the two signatures.
-
- The arguments are two tuples that contain the unique constraint and
- the signatures generated by ``create_unique_constraint_sig``.
-
- This method returns a ``ComparisonResult``.
- """
- metadata_tup = self._create_metadata_constraint_sig(
- metadata_constraint
- )
- reflected_tup = self._create_reflected_constraint_sig(
- reflected_constraint
- )
-
- meta_sig = metadata_tup.unnamed
- conn_sig = reflected_tup.unnamed
- if conn_sig != meta_sig:
- return ComparisonResult.Different(
- f"expression {conn_sig} to {meta_sig}"
- )
- else:
- return ComparisonResult.Equal()
-
- def _skip_functional_indexes(self, metadata_indexes, conn_indexes):
- conn_indexes_by_name = {c.name: c for c in conn_indexes}
-
- for idx in list(metadata_indexes):
- if idx.name in conn_indexes_by_name:
- continue
- iex = sqla_compat.is_expression_index(idx)
- if iex:
- util.warn(
- "autogenerate skipping metadata-specified "
- "expression-based index "
- f"{idx.name!r}; dialect {self.__dialect__!r} under "
- f"SQLAlchemy {sqla_compat.sqlalchemy_version} can't "
- "reflect these indexes so they can't be compared"
- )
- metadata_indexes.discard(idx)
-
- def adjust_reflected_dialect_options(
- self, reflected_object: _ReflectedConstraint, kind: str
- ) -> Dict[str, Any]:
- return reflected_object.get("dialect_options", {}) # type: ignore[return-value] # noqa: E501
-
-
-class Params(NamedTuple):
- token0: str
- tokens: List[str]
- args: List[str]
- kwargs: Dict[str, str]
-
-
-def _compare_identity_options(
- metadata_io: Union[schema.Identity, schema.Sequence, None],
- inspector_io: Union[schema.Identity, schema.Sequence, None],
- default_io: Union[schema.Identity, schema.Sequence],
- skip: Set[str],
-):
- # this can be used for identity or sequence compare.
- # default_io is an instance of IdentityOption with all attributes to the
- # default value.
- meta_d = sqla_compat._get_identity_options_dict(metadata_io)
- insp_d = sqla_compat._get_identity_options_dict(inspector_io)
-
- diff = set()
- ignored_attr = set()
-
- def check_dicts(
- meta_dict: Mapping[str, Any],
- insp_dict: Mapping[str, Any],
- default_dict: Mapping[str, Any],
- attrs: Iterable[str],
- ):
- for attr in set(attrs).difference(skip):
- meta_value = meta_dict.get(attr)
- insp_value = insp_dict.get(attr)
- if insp_value != meta_value:
- default_value = default_dict.get(attr)
- if meta_value == default_value:
- ignored_attr.add(attr)
- else:
- diff.add(attr)
-
- check_dicts(
- meta_d,
- insp_d,
- sqla_compat._get_identity_options_dict(default_io),
- set(meta_d).union(insp_d),
- )
- if sqla_compat.identity_has_dialect_kwargs:
- assert hasattr(default_io, "dialect_kwargs")
- # use only the dialect kwargs in inspector_io since metadata_io
- # can have options for many backends
- check_dicts(
- getattr(metadata_io, "dialect_kwargs", {}),
- getattr(inspector_io, "dialect_kwargs", {}),
- default_io.dialect_kwargs,
- getattr(inspector_io, "dialect_kwargs", {}),
- )
-
- return diff, ignored_attr
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py
deleted file mode 100644
index 91cd9e4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mssql.py
+++ /dev/null
@@ -1,523 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import types as sqltypes
-from sqlalchemy.schema import Column
-from sqlalchemy.schema import CreateIndex
-from sqlalchemy.sql.base import Executable
-from sqlalchemy.sql.elements import ClauseElement
-
-from .base import AddColumn
-from .base import alter_column
-from .base import alter_table
-from .base import ColumnComment
-from .base import ColumnDefault
-from .base import ColumnName
-from .base import ColumnNullable
-from .base import ColumnType
-from .base import format_column_name
-from .base import format_server_default
-from .base import format_table_name
-from .base import format_type
-from .base import RenameTable
-from .impl import DefaultImpl
-from .. import util
-from ..util import sqla_compat
-from ..util.sqla_compat import compiles
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy.dialects.mssql.base import MSDDLCompiler
- from sqlalchemy.dialects.mssql.base import MSSQLCompiler
- from sqlalchemy.engine.cursor import CursorResult
- from sqlalchemy.sql.schema import Index
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.selectable import TableClause
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .base import _ServerDefaultType
- from .impl import _ReflectedConstraint
-
-
-class MSSQLImpl(DefaultImpl):
- __dialect__ = "mssql"
- transactional_ddl = True
- batch_separator = "GO"
-
- type_synonyms = DefaultImpl.type_synonyms + ({"VARCHAR", "NVARCHAR"},)
- identity_attrs_ignore = DefaultImpl.identity_attrs_ignore + (
- "minvalue",
- "maxvalue",
- "nominvalue",
- "nomaxvalue",
- "cycle",
- "cache",
- )
-
- def __init__(self, *arg, **kw) -> None:
- super().__init__(*arg, **kw)
- self.batch_separator = self.context_opts.get(
- "mssql_batch_separator", self.batch_separator
- )
-
- def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
- result = super()._exec(construct, *args, **kw)
- if self.as_sql and self.batch_separator:
- self.static_output(self.batch_separator)
- return result
-
- def emit_begin(self) -> None:
- self.static_output("BEGIN TRANSACTION" + self.command_terminator)
-
- def emit_commit(self) -> None:
- super().emit_commit()
- if self.as_sql and self.batch_separator:
- self.static_output(self.batch_separator)
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = False,
- name: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- schema: Optional[str] = None,
- existing_type: Optional[TypeEngine] = None,
- existing_server_default: Union[
- _ServerDefaultType, Literal[False], None
- ] = None,
- existing_nullable: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- if nullable is not None:
- if type_ is not None:
- # the NULL/NOT NULL alter will handle
- # the type alteration
- existing_type = type_
- type_ = None
- elif existing_type is None:
- raise util.CommandError(
- "MS-SQL ALTER COLUMN operations "
- "with NULL or NOT NULL require the "
- "existing_type or a new type_ be passed."
- )
- elif existing_nullable is not None and type_ is not None:
- nullable = existing_nullable
-
- # the NULL/NOT NULL alter will handle
- # the type alteration
- existing_type = type_
- type_ = None
-
- elif type_ is not None:
- util.warn(
- "MS-SQL ALTER COLUMN operations that specify type_= "
- "should also specify a nullable= or "
- "existing_nullable= argument to avoid implicit conversion "
- "of NOT NULL columns to NULL."
- )
-
- used_default = False
- if sqla_compat._server_default_is_identity(
- server_default, existing_server_default
- ) or sqla_compat._server_default_is_computed(
- server_default, existing_server_default
- ):
- used_default = True
- kw["server_default"] = server_default
- kw["existing_server_default"] = existing_server_default
-
- # drop existing default constraints before changing type
- # or default, see issue #1744
- if (
- server_default is not False
- and used_default is False
- and (
- existing_server_default is not False or server_default is None
- )
- ):
- self._exec(
- _ExecDropConstraint(
- table_name,
- column_name,
- "sys.default_constraints",
- schema,
- )
- )
-
- # TODO: see why these two alter_columns can't be called
- # at once. joining them works but some of the mssql tests
- # seem to expect something different
- super().alter_column(
- table_name,
- column_name,
- nullable=nullable,
- type_=type_,
- schema=schema,
- existing_type=existing_type,
- existing_nullable=existing_nullable,
- **kw,
- )
-
- if server_default is not False and used_default is False:
- if server_default is not None:
- super().alter_column(
- table_name,
- column_name,
- schema=schema,
- server_default=server_default,
- )
-
- if name is not None:
- super().alter_column(
- table_name, column_name, schema=schema, name=name
- )
-
- def create_index(self, index: Index, **kw: Any) -> None:
- # this likely defaults to None if not present, so get()
- # should normally not return the default value. being
- # defensive in any case
- mssql_include = index.kwargs.get("mssql_include", None) or ()
- assert index.table is not None
- for col in mssql_include:
- if col not in index.table.c:
- index.table.append_column(Column(col, sqltypes.NullType))
- self._exec(CreateIndex(index, **kw))
-
- def bulk_insert( # type:ignore[override]
- self, table: Union[TableClause, Table], rows: List[dict], **kw: Any
- ) -> None:
- if self.as_sql:
- self._exec(
- "SET IDENTITY_INSERT %s ON"
- % self.dialect.identifier_preparer.format_table(table)
- )
- super().bulk_insert(table, rows, **kw)
- self._exec(
- "SET IDENTITY_INSERT %s OFF"
- % self.dialect.identifier_preparer.format_table(table)
- )
- else:
- super().bulk_insert(table, rows, **kw)
-
- def drop_column(
- self,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- **kw,
- ) -> None:
- drop_default = kw.pop("mssql_drop_default", False)
- if drop_default:
- self._exec(
- _ExecDropConstraint(
- table_name, column, "sys.default_constraints", schema
- )
- )
- drop_check = kw.pop("mssql_drop_check", False)
- if drop_check:
- self._exec(
- _ExecDropConstraint(
- table_name, column, "sys.check_constraints", schema
- )
- )
- drop_fks = kw.pop("mssql_drop_foreign_key", False)
- if drop_fks:
- self._exec(_ExecDropFKConstraint(table_name, column, schema))
- super().drop_column(table_name, column, schema=schema, **kw)
-
- def compare_server_default(
- self,
- inspector_column,
- metadata_column,
- rendered_metadata_default,
- rendered_inspector_default,
- ):
- if rendered_metadata_default is not None:
- rendered_metadata_default = re.sub(
- r"[\(\) \"\']", "", rendered_metadata_default
- )
-
- if rendered_inspector_default is not None:
- # SQL Server collapses whitespace and adds arbitrary parenthesis
- # within expressions. our only option is collapse all of it
-
- rendered_inspector_default = re.sub(
- r"[\(\) \"\']", "", rendered_inspector_default
- )
-
- return rendered_inspector_default != rendered_metadata_default
-
- def _compare_identity_default(self, metadata_identity, inspector_identity):
- diff, ignored, is_alter = super()._compare_identity_default(
- metadata_identity, inspector_identity
- )
-
- if (
- metadata_identity is None
- and inspector_identity is not None
- and not diff
- and inspector_identity.column is not None
- and inspector_identity.column.primary_key
- ):
- # mssql reflect primary keys with autoincrement as identity
- # columns. if no different attributes are present ignore them
- is_alter = False
-
- return diff, ignored, is_alter
-
- def adjust_reflected_dialect_options(
- self, reflected_object: _ReflectedConstraint, kind: str
- ) -> Dict[str, Any]:
- options: Dict[str, Any]
- options = reflected_object.get("dialect_options", {}).copy() # type: ignore[attr-defined] # noqa: E501
- if not options.get("mssql_include"):
- options.pop("mssql_include", None)
- if not options.get("mssql_clustered"):
- options.pop("mssql_clustered", None)
- return options
-
-
-class _ExecDropConstraint(Executable, ClauseElement):
- inherit_cache = False
-
- def __init__(
- self,
- tname: str,
- colname: Union[Column[Any], str],
- type_: str,
- schema: Optional[str],
- ) -> None:
- self.tname = tname
- self.colname = colname
- self.type_ = type_
- self.schema = schema
-
-
-class _ExecDropFKConstraint(Executable, ClauseElement):
- inherit_cache = False
-
- def __init__(
- self, tname: str, colname: Column[Any], schema: Optional[str]
- ) -> None:
- self.tname = tname
- self.colname = colname
- self.schema = schema
-
-
-@compiles(_ExecDropConstraint, "mssql")
-def _exec_drop_col_constraint(
- element: _ExecDropConstraint, compiler: MSSQLCompiler, **kw
-) -> str:
- schema, tname, colname, type_ = (
- element.schema,
- element.tname,
- element.colname,
- element.type_,
- )
- # from http://www.mssqltips.com/sqlservertip/1425/\
- # working-with-default-constraints-in-sql-server/
- return """declare @const_name varchar(256)
-select @const_name = QUOTENAME([name]) from %(type)s
-where parent_object_id = object_id('%(schema_dot)s%(tname)s')
-and col_name(parent_object_id, parent_column_id) = '%(colname)s'
-exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
- "type": type_,
- "tname": tname,
- "colname": colname,
- "tname_quoted": format_table_name(compiler, tname, schema),
- "schema_dot": schema + "." if schema else "",
- }
-
-
-@compiles(_ExecDropFKConstraint, "mssql")
-def _exec_drop_col_fk_constraint(
- element: _ExecDropFKConstraint, compiler: MSSQLCompiler, **kw
-) -> str:
- schema, tname, colname = element.schema, element.tname, element.colname
-
- return """declare @const_name varchar(256)
-select @const_name = QUOTENAME([name]) from
-sys.foreign_keys fk join sys.foreign_key_columns fkc
-on fk.object_id=fkc.constraint_object_id
-where fkc.parent_object_id = object_id('%(schema_dot)s%(tname)s')
-and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s'
-exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
- "tname": tname,
- "colname": colname,
- "tname_quoted": format_table_name(compiler, tname, schema),
- "schema_dot": schema + "." if schema else "",
- }
-
-
-@compiles(AddColumn, "mssql")
-def visit_add_column(element: AddColumn, compiler: MSDDLCompiler, **kw) -> str:
- return "%s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- mssql_add_column(compiler, element.column, **kw),
- )
-
-
-def mssql_add_column(
- compiler: MSDDLCompiler, column: Column[Any], **kw
-) -> str:
- return "ADD %s" % compiler.get_column_specification(column, **kw)
-
-
-@compiles(ColumnNullable, "mssql")
-def visit_column_nullable(
- element: ColumnNullable, compiler: MSDDLCompiler, **kw
-) -> str:
- return "%s %s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- format_type(compiler, element.existing_type), # type: ignore[arg-type]
- "NULL" if element.nullable else "NOT NULL",
- )
-
-
-@compiles(ColumnDefault, "mssql")
-def visit_column_default(
- element: ColumnDefault, compiler: MSDDLCompiler, **kw
-) -> str:
- # TODO: there can also be a named constraint
- # with ADD CONSTRAINT here
- return "%s ADD DEFAULT %s FOR %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_server_default(compiler, element.default),
- format_column_name(compiler, element.column_name),
- )
-
-
-@compiles(ColumnName, "mssql")
-def visit_rename_column(
- element: ColumnName, compiler: MSDDLCompiler, **kw
-) -> str:
- return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % (
- format_table_name(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- format_column_name(compiler, element.newname),
- )
-
-
-@compiles(ColumnType, "mssql")
-def visit_column_type(
- element: ColumnType, compiler: MSDDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- format_type(compiler, element.type_),
- )
-
-
-@compiles(RenameTable, "mssql")
-def visit_rename_table(
- element: RenameTable, compiler: MSDDLCompiler, **kw
-) -> str:
- return "EXEC sp_rename '%s', %s" % (
- format_table_name(compiler, element.table_name, element.schema),
- format_table_name(compiler, element.new_table_name, None),
- )
-
-
-def _add_column_comment(
- compiler: MSDDLCompiler,
- schema: Optional[str],
- tname: str,
- cname: str,
- comment: str,
-) -> str:
- schema_name = schema if schema else compiler.dialect.default_schema_name
- assert schema_name
- return (
- "exec sp_addextendedproperty 'MS_Description', {}, "
- "'schema', {}, 'table', {}, 'column', {}".format(
- compiler.sql_compiler.render_literal_value(
- comment, sqltypes.NVARCHAR()
- ),
- compiler.preparer.quote_schema(schema_name),
- compiler.preparer.quote(tname),
- compiler.preparer.quote(cname),
- )
- )
-
-
-def _update_column_comment(
- compiler: MSDDLCompiler,
- schema: Optional[str],
- tname: str,
- cname: str,
- comment: str,
-) -> str:
- schema_name = schema if schema else compiler.dialect.default_schema_name
- assert schema_name
- return (
- "exec sp_updateextendedproperty 'MS_Description', {}, "
- "'schema', {}, 'table', {}, 'column', {}".format(
- compiler.sql_compiler.render_literal_value(
- comment, sqltypes.NVARCHAR()
- ),
- compiler.preparer.quote_schema(schema_name),
- compiler.preparer.quote(tname),
- compiler.preparer.quote(cname),
- )
- )
-
-
-def _drop_column_comment(
- compiler: MSDDLCompiler, schema: Optional[str], tname: str, cname: str
-) -> str:
- schema_name = schema if schema else compiler.dialect.default_schema_name
- assert schema_name
- return (
- "exec sp_dropextendedproperty 'MS_Description', "
- "'schema', {}, 'table', {}, 'column', {}".format(
- compiler.preparer.quote_schema(schema_name),
- compiler.preparer.quote(tname),
- compiler.preparer.quote(cname),
- )
- )
-
-
-@compiles(ColumnComment, "mssql")
-def visit_column_comment(
- element: ColumnComment, compiler: MSDDLCompiler, **kw: Any
-) -> str:
- if element.comment is not None:
- if element.existing_comment is not None:
- return _update_column_comment(
- compiler,
- element.schema,
- element.table_name,
- element.column_name,
- element.comment,
- )
- else:
- return _add_column_comment(
- compiler,
- element.schema,
- element.table_name,
- element.column_name,
- element.comment,
- )
- else:
- return _drop_column_comment(
- compiler, element.schema, element.table_name, element.column_name
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py
deleted file mode 100644
index 27f808b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/mysql.py
+++ /dev/null
@@ -1,526 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import schema
-from sqlalchemy import types as sqltypes
-from sqlalchemy.sql import elements
-from sqlalchemy.sql import functions
-from sqlalchemy.sql import operators
-
-from .base import alter_table
-from .base import AlterColumn
-from .base import ColumnDefault
-from .base import ColumnName
-from .base import ColumnNullable
-from .base import ColumnType
-from .base import format_column_name
-from .base import format_server_default
-from .impl import DefaultImpl
-from .. import util
-from ..util import sqla_compat
-from ..util.sqla_compat import _is_type_bound
-from ..util.sqla_compat import compiles
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy.dialects.mysql.base import MySQLDDLCompiler
- from sqlalchemy.sql.ddl import DropConstraint
- from sqlalchemy.sql.elements import ClauseElement
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .base import _ServerDefaultType
-
-
-class MySQLImpl(DefaultImpl):
- __dialect__ = "mysql"
-
- transactional_ddl = False
- type_synonyms = DefaultImpl.type_synonyms + (
- {"BOOL", "TINYINT"},
- {"JSON", "LONGTEXT"},
- )
- type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"]
-
- def render_ddl_sql_expr(
- self,
- expr: ClauseElement,
- is_server_default: bool = False,
- is_index: bool = False,
- **kw: Any,
- ) -> str:
- # apply Grouping to index expressions;
- # see https://github.com/sqlalchemy/sqlalchemy/blob/
- # 36da2eaf3e23269f2cf28420ae73674beafd0661/
- # lib/sqlalchemy/dialects/mysql/base.py#L2191
- if is_index and (
- isinstance(expr, elements.BinaryExpression)
- or (
- isinstance(expr, elements.UnaryExpression)
- and expr.modifier not in (operators.desc_op, operators.asc_op)
- )
- or isinstance(expr, functions.FunctionElement)
- ):
- expr = elements.Grouping(expr)
-
- return super().render_ddl_sql_expr(
- expr, is_server_default=is_server_default, is_index=is_index, **kw
- )
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = False,
- name: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- schema: Optional[str] = None,
- existing_type: Optional[TypeEngine] = None,
- existing_server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = None,
- existing_nullable: Optional[bool] = None,
- autoincrement: Optional[bool] = None,
- existing_autoincrement: Optional[bool] = None,
- comment: Optional[Union[str, Literal[False]]] = False,
- existing_comment: Optional[str] = None,
- **kw: Any,
- ) -> None:
- if sqla_compat._server_default_is_identity(
- server_default, existing_server_default
- ) or sqla_compat._server_default_is_computed(
- server_default, existing_server_default
- ):
- # modifying computed or identity columns is not supported
- # the default will raise
- super().alter_column(
- table_name,
- column_name,
- nullable=nullable,
- type_=type_,
- schema=schema,
- existing_type=existing_type,
- existing_nullable=existing_nullable,
- server_default=server_default,
- existing_server_default=existing_server_default,
- **kw,
- )
- if name is not None or self._is_mysql_allowed_functional_default(
- type_ if type_ is not None else existing_type, server_default
- ):
- self._exec(
- MySQLChangeColumn(
- table_name,
- column_name,
- schema=schema,
- newname=name if name is not None else column_name,
- nullable=(
- nullable
- if nullable is not None
- else (
- existing_nullable
- if existing_nullable is not None
- else True
- )
- ),
- type_=type_ if type_ is not None else existing_type,
- default=(
- server_default
- if server_default is not False
- else existing_server_default
- ),
- autoincrement=(
- autoincrement
- if autoincrement is not None
- else existing_autoincrement
- ),
- comment=(
- comment if comment is not False else existing_comment
- ),
- )
- )
- elif (
- nullable is not None
- or type_ is not None
- or autoincrement is not None
- or comment is not False
- ):
- self._exec(
- MySQLModifyColumn(
- table_name,
- column_name,
- schema=schema,
- newname=name if name is not None else column_name,
- nullable=(
- nullable
- if nullable is not None
- else (
- existing_nullable
- if existing_nullable is not None
- else True
- )
- ),
- type_=type_ if type_ is not None else existing_type,
- default=(
- server_default
- if server_default is not False
- else existing_server_default
- ),
- autoincrement=(
- autoincrement
- if autoincrement is not None
- else existing_autoincrement
- ),
- comment=(
- comment if comment is not False else existing_comment
- ),
- )
- )
- elif server_default is not False:
- self._exec(
- MySQLAlterDefault(
- table_name, column_name, server_default, schema=schema
- )
- )
-
- def drop_constraint(
- self,
- const: Constraint,
- **kw: Any,
- ) -> None:
- if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
- return
-
- super().drop_constraint(const)
-
- def _is_mysql_allowed_functional_default(
- self,
- type_: Optional[TypeEngine],
- server_default: Optional[Union[_ServerDefaultType, Literal[False]]],
- ) -> bool:
- return (
- type_ is not None
- and type_._type_affinity is sqltypes.DateTime
- and server_default is not None
- )
-
- def compare_server_default(
- self,
- inspector_column,
- metadata_column,
- rendered_metadata_default,
- rendered_inspector_default,
- ):
- # partially a workaround for SQLAlchemy issue #3023; if the
- # column were created without "NOT NULL", MySQL may have added
- # an implicit default of '0' which we need to skip
- # TODO: this is not really covered anymore ?
- if (
- metadata_column.type._type_affinity is sqltypes.Integer
- and inspector_column.primary_key
- and not inspector_column.autoincrement
- and not rendered_metadata_default
- and rendered_inspector_default == "'0'"
- ):
- return False
- elif (
- rendered_inspector_default
- and inspector_column.type._type_affinity is sqltypes.Integer
- ):
- rendered_inspector_default = (
- re.sub(r"^'|'$", "", rendered_inspector_default)
- if rendered_inspector_default is not None
- else None
- )
- return rendered_inspector_default != rendered_metadata_default
- elif (
- rendered_metadata_default
- and metadata_column.type._type_affinity is sqltypes.String
- ):
- metadata_default = re.sub(r"^'|'$", "", rendered_metadata_default)
- return rendered_inspector_default != f"'{metadata_default}'"
- elif rendered_inspector_default and rendered_metadata_default:
- # adjust for "function()" vs. "FUNCTION" as can occur particularly
- # for the CURRENT_TIMESTAMP function on newer MariaDB versions
-
- # SQLAlchemy MySQL dialect bundles ON UPDATE into the server
- # default; adjust for this possibly being present.
- onupdate_ins = re.match(
- r"(.*) (on update.*?)(?:\(\))?$",
- rendered_inspector_default.lower(),
- )
- onupdate_met = re.match(
- r"(.*) (on update.*?)(?:\(\))?$",
- rendered_metadata_default.lower(),
- )
-
- if onupdate_ins:
- if not onupdate_met:
- return True
- elif onupdate_ins.group(2) != onupdate_met.group(2):
- return True
-
- rendered_inspector_default = onupdate_ins.group(1)
- rendered_metadata_default = onupdate_met.group(1)
-
- return re.sub(
- r"(.*?)(?:\(\))?$", r"\1", rendered_inspector_default.lower()
- ) != re.sub(
- r"(.*?)(?:\(\))?$", r"\1", rendered_metadata_default.lower()
- )
- else:
- return rendered_inspector_default != rendered_metadata_default
-
- def correct_for_autogen_constraints(
- self,
- conn_unique_constraints,
- conn_indexes,
- metadata_unique_constraints,
- metadata_indexes,
- ):
- # TODO: if SQLA 1.0, make use of "duplicates_index"
- # metadata
- removed = set()
- for idx in list(conn_indexes):
- if idx.unique:
- continue
- # MySQL puts implicit indexes on FK columns, even if
- # composite and even if MyISAM, so can't check this too easily.
- # the name of the index may be the column name or it may
- # be the name of the FK constraint.
- for col in idx.columns:
- if idx.name == col.name:
- conn_indexes.remove(idx)
- removed.add(idx.name)
- break
- for fk in col.foreign_keys:
- if fk.name == idx.name:
- conn_indexes.remove(idx)
- removed.add(idx.name)
- break
- if idx.name in removed:
- break
-
- # then remove indexes from the "metadata_indexes"
- # that we've removed from reflected, otherwise they come out
- # as adds (see #202)
- for idx in list(metadata_indexes):
- if idx.name in removed:
- metadata_indexes.remove(idx)
-
- def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
- conn_fk_by_sig = {
- self._create_reflected_constraint_sig(fk).unnamed_no_options: fk
- for fk in conn_fks
- }
- metadata_fk_by_sig = {
- self._create_metadata_constraint_sig(fk).unnamed_no_options: fk
- for fk in metadata_fks
- }
-
- for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig):
- mdfk = metadata_fk_by_sig[sig]
- cnfk = conn_fk_by_sig[sig]
- # MySQL considers RESTRICT to be the default and doesn't
- # report on it. if the model has explicit RESTRICT and
- # the conn FK has None, set it to RESTRICT
- if (
- mdfk.ondelete is not None
- and mdfk.ondelete.lower() == "restrict"
- and cnfk.ondelete is None
- ):
- cnfk.ondelete = "RESTRICT"
- if (
- mdfk.onupdate is not None
- and mdfk.onupdate.lower() == "restrict"
- and cnfk.onupdate is None
- ):
- cnfk.onupdate = "RESTRICT"
-
-
-class MariaDBImpl(MySQLImpl):
- __dialect__ = "mariadb"
-
-
-class MySQLAlterDefault(AlterColumn):
- def __init__(
- self,
- name: str,
- column_name: str,
- default: Optional[_ServerDefaultType],
- schema: Optional[str] = None,
- ) -> None:
- super(AlterColumn, self).__init__(name, schema=schema)
- self.column_name = column_name
- self.default = default
-
-
-class MySQLChangeColumn(AlterColumn):
- def __init__(
- self,
- name: str,
- column_name: str,
- schema: Optional[str] = None,
- newname: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- nullable: Optional[bool] = None,
- default: Optional[Union[_ServerDefaultType, Literal[False]]] = False,
- autoincrement: Optional[bool] = None,
- comment: Optional[Union[str, Literal[False]]] = False,
- ) -> None:
- super(AlterColumn, self).__init__(name, schema=schema)
- self.column_name = column_name
- self.nullable = nullable
- self.newname = newname
- self.default = default
- self.autoincrement = autoincrement
- self.comment = comment
- if type_ is None:
- raise util.CommandError(
- "All MySQL CHANGE/MODIFY COLUMN operations "
- "require the existing type."
- )
-
- self.type_ = sqltypes.to_instance(type_)
-
-
-class MySQLModifyColumn(MySQLChangeColumn):
- pass
-
-
-@compiles(ColumnNullable, "mysql", "mariadb")
-@compiles(ColumnName, "mysql", "mariadb")
-@compiles(ColumnDefault, "mysql", "mariadb")
-@compiles(ColumnType, "mysql", "mariadb")
-def _mysql_doesnt_support_individual(element, compiler, **kw):
- raise NotImplementedError(
- "Individual alter column constructs not supported by MySQL"
- )
-
-
-@compiles(MySQLAlterDefault, "mysql", "mariadb")
-def _mysql_alter_default(
- element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw
-) -> str:
- return "%s ALTER COLUMN %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- (
- "SET DEFAULT %s" % format_server_default(compiler, element.default)
- if element.default is not None
- else "DROP DEFAULT"
- ),
- )
-
-
-@compiles(MySQLModifyColumn, "mysql", "mariadb")
-def _mysql_modify_column(
- element: MySQLModifyColumn, compiler: MySQLDDLCompiler, **kw
-) -> str:
- return "%s MODIFY %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- _mysql_colspec(
- compiler,
- nullable=element.nullable,
- server_default=element.default,
- type_=element.type_,
- autoincrement=element.autoincrement,
- comment=element.comment,
- ),
- )
-
-
-@compiles(MySQLChangeColumn, "mysql", "mariadb")
-def _mysql_change_column(
- element: MySQLChangeColumn, compiler: MySQLDDLCompiler, **kw
-) -> str:
- return "%s CHANGE %s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- format_column_name(compiler, element.newname),
- _mysql_colspec(
- compiler,
- nullable=element.nullable,
- server_default=element.default,
- type_=element.type_,
- autoincrement=element.autoincrement,
- comment=element.comment,
- ),
- )
-
-
-def _mysql_colspec(
- compiler: MySQLDDLCompiler,
- nullable: Optional[bool],
- server_default: Optional[Union[_ServerDefaultType, Literal[False]]],
- type_: TypeEngine,
- autoincrement: Optional[bool],
- comment: Optional[Union[str, Literal[False]]],
-) -> str:
- spec = "%s %s" % (
- compiler.dialect.type_compiler.process(type_),
- "NULL" if nullable else "NOT NULL",
- )
- if autoincrement:
- spec += " AUTO_INCREMENT"
- if server_default is not False and server_default is not None:
- spec += " DEFAULT %s" % format_server_default(compiler, server_default)
- if comment:
- spec += " COMMENT %s" % compiler.sql_compiler.render_literal_value(
- comment, sqltypes.String()
- )
-
- return spec
-
-
-@compiles(schema.DropConstraint, "mysql", "mariadb")
-def _mysql_drop_constraint(
- element: DropConstraint, compiler: MySQLDDLCompiler, **kw
-) -> str:
- """Redefine SQLAlchemy's drop constraint to
- raise errors for invalid constraint type."""
-
- constraint = element.element
- if isinstance(
- constraint,
- (
- schema.ForeignKeyConstraint,
- schema.PrimaryKeyConstraint,
- schema.UniqueConstraint,
- ),
- ):
- assert not kw
- return compiler.visit_drop_constraint(element)
- elif isinstance(constraint, schema.CheckConstraint):
- # note that SQLAlchemy as of 1.2 does not yet support
- # DROP CONSTRAINT for MySQL/MariaDB, so we implement fully
- # here.
- if compiler.dialect.is_mariadb:
- return "ALTER TABLE %s DROP CONSTRAINT %s" % (
- compiler.preparer.format_table(constraint.table),
- compiler.preparer.format_constraint(constraint),
- )
- else:
- return "ALTER TABLE %s DROP CHECK %s" % (
- compiler.preparer.format_table(constraint.table),
- compiler.preparer.format_constraint(constraint),
- )
- else:
- raise NotImplementedError(
- "No generic 'DROP CONSTRAINT' in MySQL - "
- "please specify constraint type"
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py
deleted file mode 100644
index eac9912..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/oracle.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Optional
-from typing import TYPE_CHECKING
-
-from sqlalchemy.sql import sqltypes
-
-from .base import AddColumn
-from .base import alter_table
-from .base import ColumnComment
-from .base import ColumnDefault
-from .base import ColumnName
-from .base import ColumnNullable
-from .base import ColumnType
-from .base import format_column_name
-from .base import format_server_default
-from .base import format_table_name
-from .base import format_type
-from .base import IdentityColumnDefault
-from .base import RenameTable
-from .impl import DefaultImpl
-from ..util.sqla_compat import compiles
-
-if TYPE_CHECKING:
- from sqlalchemy.dialects.oracle.base import OracleDDLCompiler
- from sqlalchemy.engine.cursor import CursorResult
- from sqlalchemy.sql.schema import Column
-
-
-class OracleImpl(DefaultImpl):
- __dialect__ = "oracle"
- transactional_ddl = False
- batch_separator = "/"
- command_terminator = ""
- type_synonyms = DefaultImpl.type_synonyms + (
- {"VARCHAR", "VARCHAR2"},
- {"BIGINT", "INTEGER", "SMALLINT", "DECIMAL", "NUMERIC", "NUMBER"},
- {"DOUBLE", "FLOAT", "DOUBLE_PRECISION"},
- )
- identity_attrs_ignore = ()
-
- def __init__(self, *arg, **kw) -> None:
- super().__init__(*arg, **kw)
- self.batch_separator = self.context_opts.get(
- "oracle_batch_separator", self.batch_separator
- )
-
- def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
- result = super()._exec(construct, *args, **kw)
- if self.as_sql and self.batch_separator:
- self.static_output(self.batch_separator)
- return result
-
- def compare_server_default(
- self,
- inspector_column,
- metadata_column,
- rendered_metadata_default,
- rendered_inspector_default,
- ):
- if rendered_metadata_default is not None:
- rendered_metadata_default = re.sub(
- r"^\((.+)\)$", r"\1", rendered_metadata_default
- )
-
- rendered_metadata_default = re.sub(
- r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
- )
-
- if rendered_inspector_default is not None:
- rendered_inspector_default = re.sub(
- r"^\((.+)\)$", r"\1", rendered_inspector_default
- )
-
- rendered_inspector_default = re.sub(
- r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
- )
-
- rendered_inspector_default = rendered_inspector_default.strip()
- return rendered_inspector_default != rendered_metadata_default
-
- def emit_begin(self) -> None:
- self._exec("SET TRANSACTION READ WRITE")
-
- def emit_commit(self) -> None:
- self._exec("COMMIT")
-
-
-@compiles(AddColumn, "oracle")
-def visit_add_column(
- element: AddColumn, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- add_column(compiler, element.column, **kw),
- )
-
-
-@compiles(ColumnNullable, "oracle")
-def visit_column_nullable(
- element: ColumnNullable, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- "NULL" if element.nullable else "NOT NULL",
- )
-
-
-@compiles(ColumnType, "oracle")
-def visit_column_type(
- element: ColumnType, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- "%s" % format_type(compiler, element.type_),
- )
-
-
-@compiles(ColumnName, "oracle")
-def visit_column_name(
- element: ColumnName, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s RENAME COLUMN %s TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- format_column_name(compiler, element.newname),
- )
-
-
-@compiles(ColumnDefault, "oracle")
-def visit_column_default(
- element: ColumnDefault, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- (
- "DEFAULT %s" % format_server_default(compiler, element.default)
- if element.default is not None
- else "DEFAULT NULL"
- ),
- )
-
-
-@compiles(ColumnComment, "oracle")
-def visit_column_comment(
- element: ColumnComment, compiler: OracleDDLCompiler, **kw
-) -> str:
- ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
-
- comment = compiler.sql_compiler.render_literal_value(
- (element.comment if element.comment is not None else ""),
- sqltypes.String(),
- )
-
- return ddl.format(
- table_name=element.table_name,
- column_name=element.column_name,
- comment=comment,
- )
-
-
-@compiles(RenameTable, "oracle")
-def visit_rename_table(
- element: RenameTable, compiler: OracleDDLCompiler, **kw
-) -> str:
- return "%s RENAME TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_table_name(compiler, element.new_table_name, None),
- )
-
-
-def alter_column(compiler: OracleDDLCompiler, name: str) -> str:
- return "MODIFY %s" % format_column_name(compiler, name)
-
-
-def add_column(compiler: OracleDDLCompiler, column: Column[Any], **kw) -> str:
- return "ADD %s" % compiler.get_column_specification(column, **kw)
-
-
-@compiles(IdentityColumnDefault, "oracle")
-def visit_identity_column(
- element: IdentityColumnDefault, compiler: OracleDDLCompiler, **kw
-):
- text = "%s %s " % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- )
- if element.default is None:
- # drop identity
- text += "DROP IDENTITY"
- return text
- else:
- text += compiler.visit_identity_column(element.default)
- return text
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py
deleted file mode 100644
index cc03f45..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py
+++ /dev/null
@@ -1,864 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import logging
-import re
-from typing import Any
-from typing import cast
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import Column
-from sqlalchemy import Float
-from sqlalchemy import Identity
-from sqlalchemy import literal_column
-from sqlalchemy import Numeric
-from sqlalchemy import select
-from sqlalchemy import text
-from sqlalchemy import types as sqltypes
-from sqlalchemy.dialects.postgresql import BIGINT
-from sqlalchemy.dialects.postgresql import ExcludeConstraint
-from sqlalchemy.dialects.postgresql import INTEGER
-from sqlalchemy.schema import CreateIndex
-from sqlalchemy.sql.elements import ColumnClause
-from sqlalchemy.sql.elements import TextClause
-from sqlalchemy.sql.functions import FunctionElement
-from sqlalchemy.types import NULLTYPE
-
-from .base import alter_column
-from .base import alter_table
-from .base import AlterColumn
-from .base import ColumnComment
-from .base import format_column_name
-from .base import format_table_name
-from .base import format_type
-from .base import IdentityColumnDefault
-from .base import RenameTable
-from .impl import ComparisonResult
-from .impl import DefaultImpl
-from .. import util
-from ..autogenerate import render
-from ..operations import ops
-from ..operations import schemaobj
-from ..operations.base import BatchOperations
-from ..operations.base import Operations
-from ..util import sqla_compat
-from ..util.sqla_compat import compiles
-
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy import Index
- from sqlalchemy import UniqueConstraint
- from sqlalchemy.dialects.postgresql.array import ARRAY
- from sqlalchemy.dialects.postgresql.base import PGDDLCompiler
- from sqlalchemy.dialects.postgresql.hstore import HSTORE
- from sqlalchemy.dialects.postgresql.json import JSON
- from sqlalchemy.dialects.postgresql.json import JSONB
- from sqlalchemy.sql.elements import ClauseElement
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .base import _ServerDefaultType
- from .impl import _ReflectedConstraint
- from ..autogenerate.api import AutogenContext
- from ..autogenerate.render import _f_name
- from ..runtime.migration import MigrationContext
-
-log = logging.getLogger(__name__)
-
-
-class PostgresqlImpl(DefaultImpl):
- __dialect__ = "postgresql"
- transactional_ddl = True
- type_synonyms = DefaultImpl.type_synonyms + (
- {"FLOAT", "DOUBLE PRECISION"},
- )
-
- def create_index(self, index: Index, **kw: Any) -> None:
- # this likely defaults to None if not present, so get()
- # should normally not return the default value. being
- # defensive in any case
- postgresql_include = index.kwargs.get("postgresql_include", None) or ()
- for col in postgresql_include:
- if col not in index.table.c: # type: ignore[union-attr]
- index.table.append_column( # type: ignore[union-attr]
- Column(col, sqltypes.NullType)
- )
- self._exec(CreateIndex(index, **kw))
-
- def prep_table_for_batch(self, batch_impl, table):
- for constraint in table.constraints:
- if (
- constraint.name is not None
- and constraint.name in batch_impl.named_constraints
- ):
- self.drop_constraint(constraint)
-
- def compare_server_default(
- self,
- inspector_column,
- metadata_column,
- rendered_metadata_default,
- rendered_inspector_default,
- ):
-
- # don't do defaults for SERIAL columns
- if (
- metadata_column.primary_key
- and metadata_column is metadata_column.table._autoincrement_column
- ):
- return False
-
- conn_col_default = rendered_inspector_default
-
- if conn_col_default and re.match(
- r"nextval\('(.+?)'::regclass\)", conn_col_default
- ):
- conn_col_default = conn_col_default.replace("::regclass", "")
-
- defaults_equal = conn_col_default == rendered_metadata_default
- if defaults_equal:
- return False
-
- if None in (
- conn_col_default,
- rendered_metadata_default,
- metadata_column.server_default,
- ):
- return not defaults_equal
-
- metadata_default = metadata_column.server_default.arg
-
- if isinstance(metadata_default, str):
- if not isinstance(inspector_column.type, (Numeric, Float)):
- metadata_default = re.sub(r"^'|'$", "", metadata_default)
- metadata_default = f"'{metadata_default}'"
-
- metadata_default = literal_column(metadata_default)
-
- # run a real compare against the server
- # TODO: this seems quite a bad idea for a default that's a SQL
- # function! SQL functions are not deterministic!
- conn = self.connection
- assert conn is not None
- return not conn.scalar(
- select(literal_column(conn_col_default) == metadata_default)
- )
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = False,
- name: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- schema: Optional[str] = None,
- autoincrement: Optional[bool] = None,
- existing_type: Optional[TypeEngine] = None,
- existing_server_default: Optional[
- Union[_ServerDefaultType, Literal[False]]
- ] = None,
- existing_nullable: Optional[bool] = None,
- existing_autoincrement: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- using = kw.pop("postgresql_using", None)
-
- if using is not None and type_ is None:
- raise util.CommandError(
- "postgresql_using must be used with the type_ parameter"
- )
-
- if type_ is not None:
- self._exec(
- PostgresqlColumnType(
- table_name,
- column_name,
- type_,
- schema=schema,
- using=using,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- )
- )
-
- super().alter_column(
- table_name,
- column_name,
- nullable=nullable,
- server_default=server_default,
- name=name,
- schema=schema,
- autoincrement=autoincrement,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_autoincrement=existing_autoincrement,
- **kw,
- )
-
- def autogen_column_reflect(self, inspector, table, column_info):
- if column_info.get("default") and isinstance(
- column_info["type"], (INTEGER, BIGINT)
- ):
- seq_match = re.match(
- r"nextval\('(.+?)'::regclass\)", column_info["default"]
- )
- if seq_match:
- info = sqla_compat._exec_on_inspector(
- inspector,
- text(
- "select c.relname, a.attname "
- "from pg_class as c join "
- "pg_depend d on d.objid=c.oid and "
- "d.classid='pg_class'::regclass and "
- "d.refclassid='pg_class'::regclass "
- "join pg_class t on t.oid=d.refobjid "
- "join pg_attribute a on a.attrelid=t.oid and "
- "a.attnum=d.refobjsubid "
- "where c.relkind='S' and "
- "c.oid=cast(:seqname as regclass)"
- ),
- seqname=seq_match.group(1),
- ).first()
- if info:
- seqname, colname = info
- if colname == column_info["name"]:
- log.info(
- "Detected sequence named '%s' as "
- "owned by integer column '%s(%s)', "
- "assuming SERIAL and omitting",
- seqname,
- table.name,
- colname,
- )
- # sequence, and the owner is this column,
- # its a SERIAL - whack it!
- del column_info["default"]
-
- def correct_for_autogen_constraints(
- self,
- conn_unique_constraints,
- conn_indexes,
- metadata_unique_constraints,
- metadata_indexes,
- ):
- doubled_constraints = {
- index
- for index in conn_indexes
- if index.info.get("duplicates_constraint")
- }
-
- for ix in doubled_constraints:
- conn_indexes.remove(ix)
-
- if not sqla_compat.sqla_2:
- self._skip_functional_indexes(metadata_indexes, conn_indexes)
-
- # pg behavior regarding modifiers
- # | # | compiled sql | returned sql | regexp. group is removed |
- # | - | ---------------- | -----------------| ------------------------ |
- # | 1 | nulls first | nulls first | - |
- # | 2 | nulls last | | (? str:
- expr = expr.lower().replace('"', "").replace("'", "")
- if index.table is not None:
- # should not be needed, since include_table=False is in compile
- expr = expr.replace(f"{index.table.name.lower()}.", "")
-
- if "::" in expr:
- # strip :: cast. types can have spaces in them
- expr = re.sub(r"(::[\w ]+\w)", "", expr)
-
- while expr and expr[0] == "(" and expr[-1] == ")":
- expr = expr[1:-1]
-
- # NOTE: when parsing the connection expression this cleanup could
- # be skipped
- for rs in self._default_modifiers_re:
- if match := rs.search(expr):
- start, end = match.span(1)
- expr = expr[:start] + expr[end:]
- break
-
- while expr and expr[0] == "(" and expr[-1] == ")":
- expr = expr[1:-1]
-
- # strip casts
- cast_re = re.compile(r"cast\s*\(")
- if cast_re.match(expr):
- expr = cast_re.sub("", expr)
- # remove the as type
- expr = re.sub(r"as\s+[^)]+\)", "", expr)
- # remove spaces
- expr = expr.replace(" ", "")
- return expr
-
- def _dialect_options(
- self, item: Union[Index, UniqueConstraint]
- ) -> Tuple[Any, ...]:
- # only the positive case is returned by sqlalchemy reflection so
- # None and False are treated the same
- if item.dialect_kwargs.get("postgresql_nulls_not_distinct"):
- return ("nulls_not_distinct",)
- return ()
-
- def compare_indexes(
- self,
- metadata_index: Index,
- reflected_index: Index,
- ) -> ComparisonResult:
- msg = []
- unique_msg = self._compare_index_unique(
- metadata_index, reflected_index
- )
- if unique_msg:
- msg.append(unique_msg)
- m_exprs = metadata_index.expressions
- r_exprs = reflected_index.expressions
- if len(m_exprs) != len(r_exprs):
- msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}")
- if msg:
- # no point going further, return early
- return ComparisonResult.Different(msg)
- skip = []
- for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1):
- m_compile = self._compile_element(m_e)
- m_text = self._cleanup_index_expr(metadata_index, m_compile)
- # print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}")
- r_compile = self._compile_element(r_e)
- r_text = self._cleanup_index_expr(metadata_index, r_compile)
- # print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}")
- if m_text == r_text:
- continue # expressions these are equal
- elif m_compile.strip().endswith("_ops") and (
- " " in m_compile or ")" in m_compile # is an expression
- ):
- skip.append(
- f"expression #{pos} {m_compile!r} detected "
- "as including operator clause."
- )
- util.warn(
- f"Expression #{pos} {m_compile!r} in index "
- f"{reflected_index.name!r} detected to include "
- "an operator clause. Expression compare cannot proceed. "
- "Please move the operator clause to the "
- "``postgresql_ops`` dict to enable proper compare "
- "of the index expressions: "
- "https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501
- )
- else:
- msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}")
-
- m_options = self._dialect_options(metadata_index)
- r_options = self._dialect_options(reflected_index)
- if m_options != r_options:
- msg.extend(f"options {r_options} to {m_options}")
-
- if msg:
- return ComparisonResult.Different(msg)
- elif skip:
- # if there are other changes detected don't skip the index
- return ComparisonResult.Skip(skip)
- else:
- return ComparisonResult.Equal()
-
- def compare_unique_constraint(
- self,
- metadata_constraint: UniqueConstraint,
- reflected_constraint: UniqueConstraint,
- ) -> ComparisonResult:
- metadata_tup = self._create_metadata_constraint_sig(
- metadata_constraint
- )
- reflected_tup = self._create_reflected_constraint_sig(
- reflected_constraint
- )
-
- meta_sig = metadata_tup.unnamed
- conn_sig = reflected_tup.unnamed
- if conn_sig != meta_sig:
- return ComparisonResult.Different(
- f"expression {conn_sig} to {meta_sig}"
- )
-
- metadata_do = self._dialect_options(metadata_tup.const)
- conn_do = self._dialect_options(reflected_tup.const)
- if metadata_do != conn_do:
- return ComparisonResult.Different(
- f"expression {conn_do} to {metadata_do}"
- )
-
- return ComparisonResult.Equal()
-
- def adjust_reflected_dialect_options(
- self, reflected_object: _ReflectedConstraint, kind: str
- ) -> Dict[str, Any]:
- options: Dict[str, Any]
- options = reflected_object.get("dialect_options", {}).copy() # type: ignore[attr-defined] # noqa: E501
- if not options.get("postgresql_include"):
- options.pop("postgresql_include", None)
- return options
-
- def _compile_element(self, element: Union[ClauseElement, str]) -> str:
- if isinstance(element, str):
- return element
- return element.compile(
- dialect=self.dialect,
- compile_kwargs={"literal_binds": True, "include_table": False},
- ).string
-
- def render_ddl_sql_expr(
- self,
- expr: ClauseElement,
- is_server_default: bool = False,
- is_index: bool = False,
- **kw: Any,
- ) -> str:
- """Render a SQL expression that is typically a server default,
- index expression, etc.
-
- """
-
- # apply self_group to index expressions;
- # see https://github.com/sqlalchemy/sqlalchemy/blob/
- # 82fa95cfce070fab401d020c6e6e4a6a96cc2578/
- # lib/sqlalchemy/dialects/postgresql/base.py#L2261
- if is_index and not isinstance(expr, ColumnClause):
- expr = expr.self_group()
-
- return super().render_ddl_sql_expr(
- expr, is_server_default=is_server_default, is_index=is_index, **kw
- )
-
- def render_type(
- self, type_: TypeEngine, autogen_context: AutogenContext
- ) -> Union[str, Literal[False]]:
- mod = type(type_).__module__
- if not mod.startswith("sqlalchemy.dialects.postgresql"):
- return False
-
- if hasattr(self, "_render_%s_type" % type_.__visit_name__):
- meth = getattr(self, "_render_%s_type" % type_.__visit_name__)
- return meth(type_, autogen_context)
-
- return False
-
- def _render_HSTORE_type(
- self, type_: HSTORE, autogen_context: AutogenContext
- ) -> str:
- return cast(
- str,
- render._render_type_w_subtype(
- type_, autogen_context, "text_type", r"(.+?\(.*text_type=)"
- ),
- )
-
- def _render_ARRAY_type(
- self, type_: ARRAY, autogen_context: AutogenContext
- ) -> str:
- return cast(
- str,
- render._render_type_w_subtype(
- type_, autogen_context, "item_type", r"(.+?\()"
- ),
- )
-
- def _render_JSON_type(
- self, type_: JSON, autogen_context: AutogenContext
- ) -> str:
- return cast(
- str,
- render._render_type_w_subtype(
- type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
- ),
- )
-
- def _render_JSONB_type(
- self, type_: JSONB, autogen_context: AutogenContext
- ) -> str:
- return cast(
- str,
- render._render_type_w_subtype(
- type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
- ),
- )
-
-
-class PostgresqlColumnType(AlterColumn):
- def __init__(
- self, name: str, column_name: str, type_: TypeEngine, **kw
- ) -> None:
- using = kw.pop("using", None)
- super().__init__(name, column_name, **kw)
- self.type_ = sqltypes.to_instance(type_)
- self.using = using
-
-
-@compiles(RenameTable, "postgresql")
-def visit_rename_table(
- element: RenameTable, compiler: PGDDLCompiler, **kw
-) -> str:
- return "%s RENAME TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_table_name(compiler, element.new_table_name, None),
- )
-
-
-@compiles(PostgresqlColumnType, "postgresql")
-def visit_column_type(
- element: PostgresqlColumnType, compiler: PGDDLCompiler, **kw
-) -> str:
- return "%s %s %s %s" % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- "TYPE %s" % format_type(compiler, element.type_),
- "USING %s" % element.using if element.using else "",
- )
-
-
-@compiles(ColumnComment, "postgresql")
-def visit_column_comment(
- element: ColumnComment, compiler: PGDDLCompiler, **kw
-) -> str:
- ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
- comment = (
- compiler.sql_compiler.render_literal_value(
- element.comment, sqltypes.String()
- )
- if element.comment is not None
- else "NULL"
- )
-
- return ddl.format(
- table_name=format_table_name(
- compiler, element.table_name, element.schema
- ),
- column_name=format_column_name(compiler, element.column_name),
- comment=comment,
- )
-
-
-@compiles(IdentityColumnDefault, "postgresql")
-def visit_identity_column(
- element: IdentityColumnDefault, compiler: PGDDLCompiler, **kw
-):
- text = "%s %s " % (
- alter_table(compiler, element.table_name, element.schema),
- alter_column(compiler, element.column_name),
- )
- if element.default is None:
- # drop identity
- text += "DROP IDENTITY"
- return text
- elif element.existing_server_default is None:
- # add identity options
- text += "ADD "
- text += compiler.visit_identity_column(element.default)
- return text
- else:
- # alter identity
- diff, _, _ = element.impl._compare_identity_default(
- element.default, element.existing_server_default
- )
- identity = element.default
- for attr in sorted(diff):
- if attr == "always":
- text += "SET GENERATED %s " % (
- "ALWAYS" if identity.always else "BY DEFAULT"
- )
- else:
- text += "SET %s " % compiler.get_identity_options(
- Identity(**{attr: getattr(identity, attr)})
- )
- return text
-
-
-@Operations.register_operation("create_exclude_constraint")
-@BatchOperations.register_operation(
- "create_exclude_constraint", "batch_create_exclude_constraint"
-)
-@ops.AddConstraintOp.register_add_constraint("exclude_constraint")
-class CreateExcludeConstraintOp(ops.AddConstraintOp):
- """Represent a create exclude constraint operation."""
-
- constraint_type = "exclude"
-
- def __init__(
- self,
- constraint_name: sqla_compat._ConstraintName,
- table_name: Union[str, quoted_name],
- elements: Union[
- Sequence[Tuple[str, str]],
- Sequence[Tuple[ColumnClause[Any], str]],
- ],
- where: Optional[Union[ColumnElement[bool], str]] = None,
- schema: Optional[str] = None,
- _orig_constraint: Optional[ExcludeConstraint] = None,
- **kw,
- ) -> None:
- self.constraint_name = constraint_name
- self.table_name = table_name
- self.elements = elements
- self.where = where
- self.schema = schema
- self._orig_constraint = _orig_constraint
- self.kw = kw
-
- @classmethod
- def from_constraint( # type:ignore[override]
- cls, constraint: ExcludeConstraint
- ) -> CreateExcludeConstraintOp:
- constraint_table = sqla_compat._table_for_constraint(constraint)
- return cls(
- constraint.name,
- constraint_table.name,
- [ # type: ignore
- (expr, op) for expr, name, op in constraint._render_exprs
- ],
- where=cast("ColumnElement[bool] | None", constraint.where),
- schema=constraint_table.schema,
- _orig_constraint=constraint,
- deferrable=constraint.deferrable,
- initially=constraint.initially,
- using=constraint.using,
- )
-
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> ExcludeConstraint:
- if self._orig_constraint is not None:
- return self._orig_constraint
- schema_obj = schemaobj.SchemaObjects(migration_context)
- t = schema_obj.table(self.table_name, schema=self.schema)
- excl = ExcludeConstraint(
- *self.elements,
- name=self.constraint_name,
- where=self.where,
- **self.kw,
- )
- for (
- expr,
- name,
- oper,
- ) in excl._render_exprs:
- t.append_column(Column(name, NULLTYPE))
- t.append_constraint(excl)
- return excl
-
- @classmethod
- def create_exclude_constraint(
- cls,
- operations: Operations,
- constraint_name: str,
- table_name: str,
- *elements: Any,
- **kw: Any,
- ) -> Optional[Table]:
- """Issue an alter to create an EXCLUDE constraint using the
- current migration context.
-
- .. note:: This method is Postgresql specific, and additionally
- requires at least SQLAlchemy 1.0.
-
- e.g.::
-
- from alembic import op
-
- op.create_exclude_constraint(
- "user_excl",
- "user",
- ("period", "&&"),
- ("group", "="),
- where=("group != 'some group'"),
- )
-
- Note that the expressions work the same way as that of
- the ``ExcludeConstraint`` object itself; if plain strings are
- passed, quoting rules must be applied manually.
-
- :param name: Name of the constraint.
- :param table_name: String name of the source table.
- :param elements: exclude conditions.
- :param where: SQL expression or SQL string with optional WHERE
- clause.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within.
-
- """
- op = cls(constraint_name, table_name, elements, **kw)
- return operations.invoke(op)
-
- @classmethod
- def batch_create_exclude_constraint(
- cls,
- operations: BatchOperations,
- constraint_name: str,
- *elements: Any,
- **kw: Any,
- ) -> Optional[Table]:
- """Issue a "create exclude constraint" instruction using the
- current batch migration context.
-
- .. note:: This method is Postgresql specific, and additionally
- requires at least SQLAlchemy 1.0.
-
- .. seealso::
-
- :meth:`.Operations.create_exclude_constraint`
-
- """
- kw["schema"] = operations.impl.schema
- op = cls(constraint_name, operations.impl.table_name, elements, **kw)
- return operations.invoke(op)
-
-
-@render.renderers.dispatch_for(CreateExcludeConstraintOp)
-def _add_exclude_constraint(
- autogen_context: AutogenContext, op: CreateExcludeConstraintOp
-) -> str:
- return _exclude_constraint(op.to_constraint(), autogen_context, alter=True)
-
-
-@render._constraint_renderers.dispatch_for(ExcludeConstraint)
-def _render_inline_exclude_constraint(
- constraint: ExcludeConstraint,
- autogen_context: AutogenContext,
- namespace_metadata: MetaData,
-) -> str:
- rendered = render._user_defined_render(
- "exclude", constraint, autogen_context
- )
- if rendered is not False:
- return rendered
-
- return _exclude_constraint(constraint, autogen_context, False)
-
-
-def _postgresql_autogenerate_prefix(autogen_context: AutogenContext) -> str:
- imports = autogen_context.imports
- if imports is not None:
- imports.add("from sqlalchemy.dialects import postgresql")
- return "postgresql."
-
-
-def _exclude_constraint(
- constraint: ExcludeConstraint,
- autogen_context: AutogenContext,
- alter: bool,
-) -> str:
- opts: List[Tuple[str, Union[quoted_name, str, _f_name, None]]] = []
-
- has_batch = autogen_context._has_batch
-
- if constraint.deferrable:
- opts.append(("deferrable", str(constraint.deferrable)))
- if constraint.initially:
- opts.append(("initially", str(constraint.initially)))
- if constraint.using:
- opts.append(("using", str(constraint.using)))
- if not has_batch and alter and constraint.table.schema:
- opts.append(("schema", render._ident(constraint.table.schema)))
- if not alter and constraint.name:
- opts.append(
- ("name", render._render_gen_name(autogen_context, constraint.name))
- )
-
- def do_expr_where_opts():
- args = [
- "(%s, %r)"
- % (
- _render_potential_column(
- sqltext, # type:ignore[arg-type]
- autogen_context,
- ),
- opstring,
- )
- for sqltext, name, opstring in constraint._render_exprs
- ]
- if constraint.where is not None:
- args.append(
- "where=%s"
- % render._render_potential_expr(
- constraint.where, autogen_context
- )
- )
- args.extend(["%s=%r" % (k, v) for k, v in opts])
- return args
-
- if alter:
- args = [
- repr(render._render_gen_name(autogen_context, constraint.name))
- ]
- if not has_batch:
- args += [repr(render._ident(constraint.table.name))]
- args.extend(do_expr_where_opts())
- return "%(prefix)screate_exclude_constraint(%(args)s)" % {
- "prefix": render._alembic_autogenerate_prefix(autogen_context),
- "args": ", ".join(args),
- }
- else:
- args = do_expr_where_opts()
- return "%(prefix)sExcludeConstraint(%(args)s)" % {
- "prefix": _postgresql_autogenerate_prefix(autogen_context),
- "args": ", ".join(args),
- }
-
-
-def _render_potential_column(
- value: Union[
- ColumnClause[Any], Column[Any], TextClause, FunctionElement[Any]
- ],
- autogen_context: AutogenContext,
-) -> str:
- if isinstance(value, ColumnClause):
- if value.is_literal:
- # like literal_column("int8range(from, to)") in ExcludeConstraint
- template = "%(prefix)sliteral_column(%(name)r)"
- else:
- template = "%(prefix)scolumn(%(name)r)"
-
- return template % {
- "prefix": render._sqlalchemy_autogenerate_prefix(autogen_context),
- "name": value.name,
- }
- else:
- return render._render_potential_expr(
- value,
- autogen_context,
- wrap_in_element=isinstance(value, (TextClause, FunctionElement)),
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py b/backend/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py
deleted file mode 100644
index c260d53..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py
+++ /dev/null
@@ -1,237 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Dict
-from typing import Optional
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import cast
-from sqlalchemy import Computed
-from sqlalchemy import JSON
-from sqlalchemy import schema
-from sqlalchemy import sql
-
-from .base import alter_table
-from .base import ColumnName
-from .base import format_column_name
-from .base import format_table_name
-from .base import RenameTable
-from .impl import DefaultImpl
-from .. import util
-from ..util.sqla_compat import compiles
-
-if TYPE_CHECKING:
- from sqlalchemy.engine.reflection import Inspector
- from sqlalchemy.sql.compiler import DDLCompiler
- from sqlalchemy.sql.elements import Cast
- from sqlalchemy.sql.elements import ClauseElement
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.type_api import TypeEngine
-
- from ..operations.batch import BatchOperationsImpl
-
-
-class SQLiteImpl(DefaultImpl):
- __dialect__ = "sqlite"
-
- transactional_ddl = False
- """SQLite supports transactional DDL, but pysqlite does not:
- see: http://bugs.python.org/issue10740
- """
-
- def requires_recreate_in_batch(
- self, batch_op: BatchOperationsImpl
- ) -> bool:
- """Return True if the given :class:`.BatchOperationsImpl`
- would need the table to be recreated and copied in order to
- proceed.
-
- Normally, only returns True on SQLite when operations other
- than add_column are present.
-
- """
- for op in batch_op.batch:
- if op[0] == "add_column":
- col = op[1][1]
- if isinstance(
- col.server_default, schema.DefaultClause
- ) and isinstance(col.server_default.arg, sql.ClauseElement):
- return True
- elif (
- isinstance(col.server_default, Computed)
- and col.server_default.persisted
- ):
- return True
- elif op[0] not in ("create_index", "drop_index"):
- return True
- else:
- return False
-
- def add_constraint(self, const: Constraint, **kw: Any):
- # attempt to distinguish between an
- # auto-gen constraint and an explicit one
- if const._create_rule is None:
- raise NotImplementedError(
- "No support for ALTER of constraints in SQLite dialect. "
- "Please refer to the batch mode feature which allows for "
- "SQLite migrations using a copy-and-move strategy."
- )
- elif const._create_rule(self):
- util.warn(
- "Skipping unsupported ALTER for "
- "creation of implicit constraint. "
- "Please refer to the batch mode feature which allows for "
- "SQLite migrations using a copy-and-move strategy."
- )
-
- def drop_constraint(self, const: Constraint, **kw: Any):
- if const._create_rule is None:
- raise NotImplementedError(
- "No support for ALTER of constraints in SQLite dialect. "
- "Please refer to the batch mode feature which allows for "
- "SQLite migrations using a copy-and-move strategy."
- )
-
- def compare_server_default(
- self,
- inspector_column: Column[Any],
- metadata_column: Column[Any],
- rendered_metadata_default: Optional[str],
- rendered_inspector_default: Optional[str],
- ) -> bool:
- if rendered_metadata_default is not None:
- rendered_metadata_default = re.sub(
- r"^\((.+)\)$", r"\1", rendered_metadata_default
- )
-
- rendered_metadata_default = re.sub(
- r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
- )
-
- if rendered_inspector_default is not None:
- rendered_inspector_default = re.sub(
- r"^\((.+)\)$", r"\1", rendered_inspector_default
- )
-
- rendered_inspector_default = re.sub(
- r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
- )
-
- return rendered_inspector_default != rendered_metadata_default
-
- def _guess_if_default_is_unparenthesized_sql_expr(
- self, expr: Optional[str]
- ) -> bool:
- """Determine if a server default is a SQL expression or a constant.
-
- There are too many assertions that expect server defaults to round-trip
- identically without parenthesis added so we will add parens only in
- very specific cases.
-
- """
- if not expr:
- return False
- elif re.match(r"^[0-9\.]$", expr):
- return False
- elif re.match(r"^'.+'$", expr):
- return False
- elif re.match(r"^\(.+\)$", expr):
- return False
- else:
- return True
-
- def autogen_column_reflect(
- self,
- inspector: Inspector,
- table: Table,
- column_info: Dict[str, Any],
- ) -> None:
- # SQLite expression defaults require parenthesis when sent
- # as DDL
- if self._guess_if_default_is_unparenthesized_sql_expr(
- column_info.get("default", None)
- ):
- column_info["default"] = "(%s)" % (column_info["default"],)
-
- def render_ddl_sql_expr(
- self, expr: ClauseElement, is_server_default: bool = False, **kw
- ) -> str:
- # SQLite expression defaults require parenthesis when sent
- # as DDL
- str_expr = super().render_ddl_sql_expr(
- expr, is_server_default=is_server_default, **kw
- )
-
- if (
- is_server_default
- and self._guess_if_default_is_unparenthesized_sql_expr(str_expr)
- ):
- str_expr = "(%s)" % (str_expr,)
- return str_expr
-
- def cast_for_batch_migrate(
- self,
- existing: Column[Any],
- existing_transfer: Dict[str, Union[TypeEngine, Cast]],
- new_type: TypeEngine,
- ) -> None:
- if (
- existing.type._type_affinity is not new_type._type_affinity
- and not isinstance(new_type, JSON)
- ):
- existing_transfer["expr"] = cast(
- existing_transfer["expr"], new_type
- )
-
- def correct_for_autogen_constraints(
- self,
- conn_unique_constraints,
- conn_indexes,
- metadata_unique_constraints,
- metadata_indexes,
- ):
- self._skip_functional_indexes(metadata_indexes, conn_indexes)
-
-
-@compiles(RenameTable, "sqlite")
-def visit_rename_table(
- element: RenameTable, compiler: DDLCompiler, **kw
-) -> str:
- return "%s RENAME TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_table_name(compiler, element.new_table_name, None),
- )
-
-
-@compiles(ColumnName, "sqlite")
-def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
- return "%s RENAME COLUMN %s TO %s" % (
- alter_table(compiler, element.table_name, element.schema),
- format_column_name(compiler, element.column_name),
- format_column_name(compiler, element.newname),
- )
-
-
-# @compiles(AddColumn, 'sqlite')
-# def visit_add_column(element, compiler, **kw):
-# return "%s %s" % (
-# alter_table(compiler, element.table_name, element.schema),
-# add_column(compiler, element.column, **kw)
-# )
-
-
-# def add_column(compiler, column, **kw):
-# text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
-# need to modify SQLAlchemy so that the CHECK associated with a Boolean
-# or Enum gets placed as part of the column constraints, not the Table
-# see ticket 98
-# for const in column.constraints:
-# text += compiler.process(AddConstraint(const))
-# return text
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/environment.py b/backend/.venv/lib/python3.12/site-packages/alembic/environment.py
deleted file mode 100644
index adfc93e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/environment.py
+++ /dev/null
@@ -1 +0,0 @@
-from .runtime.environment import * # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/migration.py b/backend/.venv/lib/python3.12/site-packages/alembic/migration.py
deleted file mode 100644
index 02626e2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/migration.py
+++ /dev/null
@@ -1 +0,0 @@
-from .runtime.migration import * # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/op.py b/backend/.venv/lib/python3.12/site-packages/alembic/op.py
deleted file mode 100644
index f3f5fac..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/op.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .operations.base import Operations
-
-# create proxy functions for
-# each method on the Operations class.
-Operations.create_module_class_proxy(globals(), locals())
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/op.pyi b/backend/.venv/lib/python3.12/site-packages/alembic/op.pyi
deleted file mode 100644
index 40a480c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/op.pyi
+++ /dev/null
@@ -1,1384 +0,0 @@
-# ### this file stubs are generated by tools/write_pyi.py - do not edit ###
-# ### imports are manually managed
-from __future__ import annotations
-
-from contextlib import contextmanager
-from typing import Any
-from typing import Awaitable
-from typing import Callable
-from typing import Dict
-from typing import Iterator
-from typing import List
-from typing import Literal
-from typing import Mapping
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-if TYPE_CHECKING:
- from sqlalchemy.engine import Connection
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.elements import conv
- from sqlalchemy.sql.elements import TextClause
- from sqlalchemy.sql.expression import TableClause
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.type_api import TypeEngine
- from sqlalchemy.util import immutabledict
-
- from .ddl.base import _ServerDefaultType
- from .operations.base import BatchOperations
- from .operations.ops import AddColumnOp
- from .operations.ops import AddConstraintOp
- from .operations.ops import AlterColumnOp
- from .operations.ops import AlterTableOp
- from .operations.ops import BulkInsertOp
- from .operations.ops import CreateIndexOp
- from .operations.ops import CreateTableCommentOp
- from .operations.ops import CreateTableOp
- from .operations.ops import DropColumnOp
- from .operations.ops import DropConstraintOp
- from .operations.ops import DropIndexOp
- from .operations.ops import DropTableCommentOp
- from .operations.ops import DropTableOp
- from .operations.ops import ExecuteSQLOp
- from .operations.ops import MigrateOperation
- from .runtime.migration import MigrationContext
- from .util.sqla_compat import _literal_bindparam
-
-_T = TypeVar("_T")
-_C = TypeVar("_C", bound=Callable[..., Any])
-
-### end imports ###
-
-def add_column(
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
-) -> None:
- """Issue an "add column" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy import Column, String
-
- op.add_column("organization", Column("name", String()))
-
- The :meth:`.Operations.add_column` method typically corresponds
- to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope
- of this command, the column's name, datatype, nullability,
- and optional server-generated defaults may be indicated.
-
- .. note::
-
- Not all contraint types may be indicated with this directive.
- PRIMARY KEY, NOT NULL, FOREIGN KEY, and CHECK are honored, UNIQUE
- is currently not.
-
- .. versionadded:: 1.18.2 Added support for PRIMARY KEY to be
- emitted within :meth:`.Operations.add_column`.
-
- As of 1.18.2, the following :class:`~sqlalchemy.schema.Column`
- parameters are **ignored**:
-
- * :paramref:`~sqlalchemy.schema.Column.unique` - use the
- :meth:`.Operations.create_unique_constraint` method
- * :paramref:`~sqlalchemy.schema.Column.index` - use the
- :meth:`.Operations.create_index` method
-
-
- The provided :class:`~sqlalchemy.schema.Column` object may include a
- :class:`~sqlalchemy.schema.ForeignKey` constraint directive,
- referencing a remote table name. By default, Alembic will automatically
- emit a second ALTER statement in order to add the single-column FOREIGN
- KEY constraint separately::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- )
-
- To render the FOREIGN KEY constraint inline within the ADD COLUMN
- directive, use the ``inline_references`` parameter. This can improve
- performance on large tables since the constraint is marked as valid
- immediately for nullable columns::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- inline_references=True,
- )
-
- The column argument passed to :meth:`.Operations.add_column` is a
- :class:`~sqlalchemy.schema.Column` construct, used in the same way it's
- used in SQLAlchemy. In particular, values or functions to be indicated
- as producing the column's default value on the database side are
- specified using the ``server_default`` parameter, and not ``default``
- which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the column add
- op.add_column(
- "account",
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- :param table_name: String name of the parent table.
- :param column: a :class:`sqlalchemy.schema.Column` object
- representing the new column.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator
- when creating the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param inline_references: If True, renders FOREIGN KEY constraints
- inline within the ADD COLUMN directive using REFERENCES syntax,
- rather than as a separate ALTER TABLE ADD CONSTRAINT statement.
- This is supported by PostgreSQL, Oracle, MySQL 5.7+, and
- MariaDB 10.5+.
-
- .. versionadded:: 1.18.2
-
- """
-
-def alter_column(
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- comment: Union[str, Literal[False], None] = False,
- server_default: Union[_ServerDefaultType, None, Literal[False]] = False,
- new_column_name: Optional[str] = None,
- type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
- existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- **kw: Any,
-) -> None:
- r"""Issue an "alter column" instruction using the
- current migration context.
-
- Generally, only that aspect of the column which
- is being changed, i.e. name, type, nullability,
- default, needs to be specified. Multiple changes
- can also be specified at once and the backend should
- "do the right thing", emitting each change either
- separately or together as the backend allows.
-
- MySQL has special requirements here, since MySQL
- cannot ALTER a column without a full specification.
- When producing MySQL-compatible migration files,
- it is recommended that the ``existing_type``,
- ``existing_server_default``, and ``existing_nullable``
- parameters be present, if not being altered.
-
- Type changes which are against the SQLAlchemy
- "schema" types :class:`~sqlalchemy.types.Boolean`
- and :class:`~sqlalchemy.types.Enum` may also
- add or drop constraints which accompany those
- types on backends that don't support them natively.
- The ``existing_type`` argument is
- used in this case to identify and remove a previous
- constraint that was bound to the type object.
-
- :param table_name: string name of the target table.
- :param column_name: string name of the target column,
- as it exists before the operation begins.
- :param nullable: Optional; specify ``True`` or ``False``
- to alter the column's nullability.
- :param server_default: Optional; specify a string
- SQL expression, :func:`~sqlalchemy.sql.expression.text`,
- or :class:`~sqlalchemy.schema.DefaultClause` to indicate
- an alteration to the column's default value.
- Set to ``None`` to have the default removed.
- :param comment: optional string text of a new comment to add to the
- column.
- :param new_column_name: Optional; specify a string name here to
- indicate the new name within a column rename operation.
- :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
- type object to specify a change to the column's type.
- For SQLAlchemy types that also indicate a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
- the constraint is also generated.
- :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column;
- currently understood by the MySQL dialect.
- :param existing_type: Optional; a
- :class:`~sqlalchemy.types.TypeEngine`
- type object to specify the previous type. This
- is required for all MySQL column alter operations that
- don't otherwise specify a new type, as well as for
- when nullability is being changed on a SQL Server
- column. It is also used if the type is a so-called
- SQLAlchemy "schema" type which may define a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`,
- :class:`~sqlalchemy.types.Enum`),
- so that the constraint can be dropped.
- :param existing_server_default: Optional; The existing
- default value of the column. Required on MySQL if
- an existing default is not being changed; else MySQL
- removes the default.
- :param existing_nullable: Optional; the existing nullability
- of the column. Required on MySQL if the existing nullability
- is not being changed; else MySQL sets this to NULL.
- :param existing_autoincrement: Optional; the existing autoincrement
- of the column. Used for MySQL's system of altering a column
- that specifies ``AUTO_INCREMENT``.
- :param existing_comment: string text of the existing comment on the
- column to be maintained. Required on MySQL if the existing comment
- on the column is not being changed.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param postgresql_using: String argument which will indicate a
- SQL expression to render within the Postgresql-specific USING clause
- within ALTER COLUMN. This string is taken directly as raw SQL which
- must explicitly include any necessary quoting or escaping of tokens
- within the expression.
-
- """
-
-@contextmanager
-def batch_alter_table(
- table_name: str,
- schema: Optional[str] = None,
- recreate: Literal["auto", "always", "never"] = "auto",
- partial_reordering: list[tuple[str, ...]] | None = None,
- copy_from: Optional[Table] = None,
- table_args: Tuple[Any, ...] = (),
- table_kwargs: Mapping[str, Any] = immutabledict({}),
- reflect_args: Tuple[Any, ...] = (),
- reflect_kwargs: Mapping[str, Any] = immutabledict({}),
- naming_convention: Optional[Dict[str, str]] = None,
-) -> Iterator[BatchOperations]:
- """Invoke a series of per-table migrations in batch.
-
- Batch mode allows a series of operations specific to a table
- to be syntactically grouped together, and allows for alternate
- modes of table migration, in particular the "recreate" style of
- migration required by SQLite.
-
- "recreate" style is as follows:
-
- 1. A new table is created with the new specification, based on the
- migration directives within the batch, using a temporary name.
-
- 2. the data copied from the existing table to the new table.
-
- 3. the existing table is dropped.
-
- 4. the new table is renamed to the existing table name.
-
- The directive by default will only use "recreate" style on the
- SQLite backend, and only if directives are present which require
- this form, e.g. anything other than ``add_column()``. The batch
- operation on other backends will proceed using standard ALTER TABLE
- operations.
-
- The method is used as a context manager, which returns an instance
- of :class:`.BatchOperations`; this object is the same as
- :class:`.Operations` except that table names and schema names
- are omitted. E.g.::
-
- with op.batch_alter_table("some_table") as batch_op:
- batch_op.add_column(Column("foo", Integer))
- batch_op.drop_column("bar")
-
- The operations within the context manager are invoked at once
- when the context is ended. When run against SQLite, if the
- migrations include operations not supported by SQLite's ALTER TABLE,
- the entire table will be copied to a new one with the new
- specification, moving all data across as well.
-
- The copy operation by default uses reflection to retrieve the current
- structure of the table, and therefore :meth:`.batch_alter_table`
- in this mode requires that the migration is run in "online" mode.
- The ``copy_from`` parameter may be passed which refers to an existing
- :class:`.Table` object, which will bypass this reflection step.
-
- .. note:: The table copy operation will currently not copy
- CHECK constraints, and may not copy UNIQUE constraints that are
- unnamed, as is possible on SQLite. See the section
- :ref:`sqlite_batch_constraints` for workarounds.
-
- :param table_name: name of table
- :param schema: optional schema name.
- :param recreate: under what circumstances the table should be
- recreated. At its default of ``"auto"``, the SQLite dialect will
- recreate the table if any operations other than ``add_column()``,
- ``create_index()``, or ``drop_index()`` are
- present. Other options include ``"always"`` and ``"never"``.
- :param copy_from: optional :class:`~sqlalchemy.schema.Table` object
- that will act as the structure of the table being copied. If omitted,
- table reflection is used to retrieve the structure of the table.
-
- .. seealso::
-
- :ref:`batch_offline_mode`
-
- :paramref:`~.Operations.batch_alter_table.reflect_args`
-
- :paramref:`~.Operations.batch_alter_table.reflect_kwargs`
-
- :param reflect_args: a sequence of additional positional arguments that
- will be applied to the table structure being reflected / copied;
- this may be used to pass column and constraint overrides to the
- table that will be reflected, in lieu of passing the whole
- :class:`~sqlalchemy.schema.Table` using
- :paramref:`~.Operations.batch_alter_table.copy_from`.
- :param reflect_kwargs: a dictionary of additional keyword arguments
- that will be applied to the table structure being copied; this may be
- used to pass additional table and reflection options to the table that
- will be reflected, in lieu of passing the whole
- :class:`~sqlalchemy.schema.Table` using
- :paramref:`~.Operations.batch_alter_table.copy_from`.
- :param table_args: a sequence of additional positional arguments that
- will be applied to the new :class:`~sqlalchemy.schema.Table` when
- created, in addition to those copied from the source table.
- This may be used to provide additional constraints such as CHECK
- constraints that may not be reflected.
- :param table_kwargs: a dictionary of additional keyword arguments
- that will be applied to the new :class:`~sqlalchemy.schema.Table`
- when created, in addition to those copied from the source table.
- This may be used to provide for additional table options that may
- not be reflected.
- :param naming_convention: a naming convention dictionary of the form
- described at :ref:`autogen_naming_conventions` which will be applied
- to the :class:`~sqlalchemy.schema.MetaData` during the reflection
- process. This is typically required if one wants to drop SQLite
- constraints, as these constraints will not have names when
- reflected on this backend. Requires SQLAlchemy **0.9.4** or greater.
-
- .. seealso::
-
- :ref:`dropping_sqlite_foreign_keys`
-
- :param partial_reordering: a list of tuples, each suggesting a desired
- ordering of two or more columns in the newly created table. Requires
- that :paramref:`.batch_alter_table.recreate` is set to ``"always"``.
- Examples, given a table with columns "a", "b", "c", and "d":
-
- Specify the order of all columns::
-
- with op.batch_alter_table(
- "some_table",
- recreate="always",
- partial_reordering=[("c", "d", "a", "b")],
- ) as batch_op:
- pass
-
- Ensure "d" appears before "c", and "b", appears before "a"::
-
- with op.batch_alter_table(
- "some_table",
- recreate="always",
- partial_reordering=[("d", "c"), ("b", "a")],
- ) as batch_op:
- pass
-
- The ordering of columns not included in the partial_reordering
- set is undefined. Therefore it is best to specify the complete
- ordering of all columns for best results.
-
- .. note:: batch mode requires SQLAlchemy 0.8 or above.
-
- .. seealso::
-
- :ref:`batch_migrations`
-
- """
-
-def bulk_insert(
- table: Union[Table, TableClause],
- rows: List[Dict[str, Any]],
- *,
- multiinsert: bool = True,
-) -> None:
- """Issue a "bulk insert" operation using the current
- migration context.
-
- This provides a means of representing an INSERT of multiple rows
- which works equally well in the context of executing on a live
- connection as well as that of generating a SQL script. In the
- case of a SQL script, the values are rendered inline into the
- statement.
-
- e.g.::
-
- from alembic import op
- from datetime import date
- from sqlalchemy.sql import table, column
- from sqlalchemy import String, Integer, Date
-
- # Create an ad-hoc table to use for the insert statement.
- accounts_table = table(
- "account",
- column("id", Integer),
- column("name", String),
- column("create_date", Date),
- )
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": date(2010, 10, 5),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": date(2007, 5, 27),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": date(2008, 8, 15),
- },
- ],
- )
-
- When using --sql mode, some datatypes may not render inline
- automatically, such as dates and other special types. When this
- issue is present, :meth:`.Operations.inline_literal` may be used::
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": op.inline_literal("2010-10-05"),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": op.inline_literal("2007-05-27"),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": op.inline_literal("2008-08-15"),
- },
- ],
- multiinsert=False,
- )
-
- When using :meth:`.Operations.inline_literal` in conjunction with
- :meth:`.Operations.bulk_insert`, in order for the statement to work
- in "online" (e.g. non --sql) mode, the
- :paramref:`~.Operations.bulk_insert.multiinsert`
- flag should be set to ``False``, which will have the effect of
- individual INSERT statements being emitted to the database, each
- with a distinct VALUES clause, so that the "inline" values can
- still be rendered, rather than attempting to pass the values
- as bound parameters.
-
- :param table: a table object which represents the target of the INSERT.
-
- :param rows: a list of dictionaries indicating rows.
-
- :param multiinsert: when at its default of True and --sql mode is not
- enabled, the INSERT statement will be executed using
- "executemany()" style, where all elements in the list of
- dictionaries are passed as bound parameters in a single
- list. Setting this to False results in individual INSERT
- statements being emitted per parameter set, and is needed
- in those cases where non-literal values are present in the
- parameter sets.
-
- """
-
-def create_check_constraint(
- constraint_name: Optional[str],
- table_name: str,
- condition: Union[str, ColumnElement[bool], TextClause],
- *,
- schema: Optional[str] = None,
- **kw: Any,
-) -> None:
- """Issue a "create check constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy.sql import column, func
-
- op.create_check_constraint(
- "ck_user_name_len",
- "user",
- func.len(column("name")) > 5,
- )
-
- CHECK constraints are usually against a SQL expression, so ad-hoc
- table metadata is usually needed. The function will convert the given
- arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound
- to an anonymous table in order to emit the CREATE statement.
-
- :param name: Name of the check constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param condition: SQL expression that's the condition of the
- constraint. Can be a string or SQLAlchemy expression language
- structure.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
-
-def create_exclude_constraint(
- constraint_name: str, table_name: str, *elements: Any, **kw: Any
-) -> Optional[Table]:
- """Issue an alter to create an EXCLUDE constraint using the
- current migration context.
-
- .. note:: This method is Postgresql specific, and additionally
- requires at least SQLAlchemy 1.0.
-
- e.g.::
-
- from alembic import op
-
- op.create_exclude_constraint(
- "user_excl",
- "user",
- ("period", "&&"),
- ("group", "="),
- where=("group != 'some group'"),
- )
-
- Note that the expressions work the same way as that of
- the ``ExcludeConstraint`` object itself; if plain strings are
- passed, quoting rules must be applied manually.
-
- :param name: Name of the constraint.
- :param table_name: String name of the source table.
- :param elements: exclude conditions.
- :param where: SQL expression or SQL string with optional WHERE
- clause.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within.
-
- """
-
-def create_foreign_key(
- constraint_name: Optional[str],
- source_table: str,
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- *,
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- source_schema: Optional[str] = None,
- referent_schema: Optional[str] = None,
- **dialect_kw: Any,
-) -> None:
- """Issue a "create foreign key" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_foreign_key(
- "fk_user_address",
- "address",
- "user",
- ["user_id"],
- ["id"],
- )
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.ForeignKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the foreign key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param source_table: String name of the source table.
- :param referent_table: String name of the destination table.
- :param local_cols: a list of string column names in the
- source table.
- :param remote_cols: a list of string column names in the
- remote table.
- :param onupdate: Optional string. If set, emit ON UPDATE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param ondelete: Optional string. If set, emit ON DELETE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param deferrable: optional bool. If set, emit DEFERRABLE or NOT
- DEFERRABLE when issuing DDL for this constraint.
- :param source_schema: Optional schema name of the source table.
- :param referent_schema: Optional schema name of the destination table.
-
- """
-
-def create_index(
- index_name: Optional[str],
- table_name: str,
- columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
- *,
- schema: Optional[str] = None,
- unique: bool = False,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
-) -> None:
- r"""Issue a "create index" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_index("ik_test", "t1", ["foo", "bar"])
-
- Functional indexes can be produced by using the
- :func:`sqlalchemy.sql.expression.text` construct::
-
- from alembic import op
- from sqlalchemy import text
-
- op.create_index("ik_test", "t1", [text("lower(foo)")])
-
- :param index_name: name of the index.
- :param table_name: name of the owning table.
- :param columns: a list consisting of string column names and/or
- :func:`~sqlalchemy.sql.expression.text` constructs.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param unique: If True, create a unique index.
-
- :param quote: Force quoting of this column's name on or off,
- corresponding to ``True`` or ``False``. When left at its default
- of ``None``, the column identifier will be quoted according to
- whether the name is case sensitive (identifiers with at least one
- upper case character are treated as case sensitive), or if it's a
- reserved word. This flag is only needed to force quoting of a
- reserved word which is not known by the SQLAlchemy dialect.
-
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """
-
-def create_primary_key(
- constraint_name: Optional[str],
- table_name: str,
- columns: List[str],
- *,
- schema: Optional[str] = None,
-) -> None:
- """Issue a "create primary key" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_primary_key("pk_my_table", "my_table", ["id", "version"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.PrimaryKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the primary key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the target table.
- :param columns: a list of string column names to be applied to the
- primary key constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
-
-def create_table(
- table_name: str,
- *columns: SchemaItem,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
-) -> Table:
- r"""Issue a "create table" instruction using the current migration
- context.
-
- This directive receives an argument list similar to that of the
- traditional :class:`sqlalchemy.schema.Table` construct, but without the
- metadata::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- Note that :meth:`.create_table` accepts
- :class:`~sqlalchemy.schema.Column`
- constructs directly from the SQLAlchemy library. In particular,
- default values to be created on the database side are
- specified using the ``server_default`` parameter, and not
- ``default`` which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the "timestamp" column
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- The function also returns a newly created
- :class:`~sqlalchemy.schema.Table` object, corresponding to the table
- specification given, which is suitable for
- immediate SQL operations, in particular
- :meth:`.Operations.bulk_insert`::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- account_table = op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- op.bulk_insert(
- account_table,
- [
- {"name": "A1", "description": "account 1"},
- {"name": "A2", "description": "account 2"},
- ],
- )
-
- :param table_name: Name of the table
- :param \*columns: collection of :class:`~sqlalchemy.schema.Column`
- objects within
- the table, as well as optional :class:`~sqlalchemy.schema.Constraint`
- objects
- and :class:`~.sqlalchemy.schema.Index` objects.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- :return: the :class:`~sqlalchemy.schema.Table` object corresponding
- to the parameters given.
-
- """
-
-def create_table_comment(
- table_name: str,
- comment: Optional[str],
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
-) -> None:
- """Emit a COMMENT ON operation to set the comment for a table.
-
- :param table_name: string name of the target table.
- :param comment: string value of the comment being registered against
- the specified table.
- :param existing_comment: String value of a comment
- already registered on the specified table, used within autogenerate
- so that the operation is reversible, but not required for direct
- use.
-
- .. seealso::
-
- :meth:`.Operations.drop_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """
-
-def create_unique_constraint(
- constraint_name: Optional[str],
- table_name: str,
- columns: Sequence[str],
- *,
- schema: Optional[str] = None,
- **kw: Any,
-) -> Any:
- """Issue a "create unique constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- op.create_unique_constraint("uq_user_name", "user", ["name"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.UniqueConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param name: Name of the unique constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param columns: a list of string column names in the
- source table.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
-
-def drop_column(
- table_name: str,
- column_name: str,
- *,
- schema: Optional[str] = None,
- **kw: Any,
-) -> None:
- """Issue a "drop column" instruction using the current
- migration context.
-
- e.g.::
-
- drop_column("organization", "account_id")
-
- :param table_name: name of table
- :param column_name: name of column
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param mssql_drop_check: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the CHECK constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.check_constraints,
- then exec's a separate DROP CONSTRAINT for that constraint.
- :param mssql_drop_default: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the DEFAULT constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.default_constraints,
- then exec's a separate DROP CONSTRAINT for that default.
- :param mssql_drop_foreign_key: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop a single FOREIGN KEY constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from
- sys.foreign_keys/sys.foreign_key_columns,
- then exec's a separate DROP CONSTRAINT for that default. Only
- works if the column has exactly one FK constraint which refers to
- it, at the moment.
- """
-
-def drop_constraint(
- constraint_name: str,
- table_name: str,
- type_: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
-) -> None:
- r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
-
- :param constraint_name: name of the constraint.
- :param table_name: table name.
- :param type\_: optional, required on MySQL. can be
- 'foreignkey', 'primary', 'unique', or 'check'.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the constraint
-
- .. versionadded:: 1.16.0
-
- """
-
-def drop_index(
- index_name: str,
- table_name: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
-) -> None:
- r"""Issue a "drop index" instruction using the current
- migration context.
-
- e.g.::
-
- drop_index("accounts")
-
- :param index_name: name of the index.
- :param table_name: name of the owning table. Some
- backends such as Microsoft SQL Server require this.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """
-
-def drop_table(
- table_name: str,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
-) -> None:
- r"""Issue a "drop table" instruction using the current
- migration context.
-
-
- e.g.::
-
- drop_table("accounts")
-
- :param table_name: Name of the table
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- """
-
-def drop_table_comment(
- table_name: str,
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
-) -> None:
- """Issue a "drop table comment" operation to
- remove an existing comment set on a table.
-
- :param table_name: string name of the target table.
- :param existing_comment: An optional string value of a comment already
- registered on the specified table.
-
- .. seealso::
-
- :meth:`.Operations.create_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """
-
-def execute(
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
-) -> None:
- r"""Execute the given SQL using the current migration context.
-
- The given SQL can be a plain string, e.g.::
-
- op.execute("INSERT INTO table (foo) VALUES ('some value')")
-
- Or it can be any kind of Core SQL Expression construct, such as
- below where we use an update construct::
-
- from sqlalchemy.sql import table, column
- from sqlalchemy import String
- from alembic import op
-
- account = table("account", column("name", String))
- op.execute(
- account.update()
- .where(account.c.name == op.inline_literal("account 1"))
- .values({"name": op.inline_literal("account 2")})
- )
-
- Above, we made use of the SQLAlchemy
- :func:`sqlalchemy.sql.expression.table` and
- :func:`sqlalchemy.sql.expression.column` constructs to make a brief,
- ad-hoc table construct just for our UPDATE statement. A full
- :class:`~sqlalchemy.schema.Table` construct of course works perfectly
- fine as well, though note it's a recommended practice to at least
- ensure the definition of a table is self-contained within the migration
- script, rather than imported from a module that may break compatibility
- with older migrations.
-
- In a SQL script context, the statement is emitted directly to the
- output stream. There is *no* return result, however, as this
- function is oriented towards generating a change script
- that can run in "offline" mode. Additionally, parameterized
- statements are discouraged here, as they *will not work* in offline
- mode. Above, we use :meth:`.inline_literal` where parameters are
- to be used.
-
- For full interaction with a connected database where parameters can
- also be used normally, use the "bind" available from the context::
-
- from alembic import op
-
- connection = op.get_bind()
-
- connection.execute(
- account.update()
- .where(account.c.name == "account 1")
- .values({"name": "account 2"})
- )
-
- Additionally, when passing the statement as a plain string, it is first
- coerced into a :func:`sqlalchemy.sql.expression.text` construct
- before being passed along. In the less likely case that the
- literal SQL string contains a colon, it must be escaped with a
- backslash, as::
-
- op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')")
-
-
- :param sqltext: Any legal SQLAlchemy expression, including:
-
- * a string
- * a :func:`sqlalchemy.sql.expression.text` construct.
- * a :func:`sqlalchemy.sql.expression.insert` construct.
- * a :func:`sqlalchemy.sql.expression.update` construct.
- * a :func:`sqlalchemy.sql.expression.delete` construct.
- * Any "executable" described in SQLAlchemy Core documentation,
- noting that no result set is returned.
-
- .. note:: when passing a plain string, the statement is coerced into
- a :func:`sqlalchemy.sql.expression.text` construct. This construct
- considers symbols with colons, e.g. ``:foo`` to be bound parameters.
- To avoid this, ensure that colon symbols are escaped, e.g.
- ``\:foo``.
-
- :param execution_options: Optional dictionary of
- execution options, will be passed to
- :meth:`sqlalchemy.engine.Connection.execution_options`.
- """
-
-def f(name: str) -> conv:
- """Indicate a string name that has already had a naming convention
- applied to it.
-
- This feature combines with the SQLAlchemy ``naming_convention`` feature
- to disambiguate constraint names that have already had naming
- conventions applied to them, versus those that have not. This is
- necessary in the case that the ``"%(constraint_name)s"`` token
- is used within a naming convention, so that it can be identified
- that this particular name should remain fixed.
-
- If the :meth:`.Operations.f` is used on a constraint, the naming
- convention will not take effect::
-
- op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x")))
-
- Above, the CHECK constraint generated will have the name
- ``ck_bool_t_x`` regardless of whether or not a naming convention is
- in use.
-
- Alternatively, if a naming convention is in use, and 'f' is not used,
- names will be converted along conventions. If the ``target_metadata``
- contains the naming convention
- ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the
- output of the following::
-
- op.add_column("t", "x", Boolean(name="x"))
-
- will be::
-
- CONSTRAINT ck_bool_t_x CHECK (x in (1, 0)))
-
- The function is rendered in the output of autogenerate when
- a particular constraint name is already converted.
-
- """
-
-def get_bind() -> Connection:
- """Return the current 'bind'.
-
- Under normal circumstances, this is the
- :class:`~sqlalchemy.engine.Connection` currently being used
- to emit SQL to the database.
-
- In a SQL script context, this value is ``None``. [TODO: verify this]
-
- """
-
-def get_context() -> MigrationContext:
- """Return the :class:`.MigrationContext` object that's
- currently in use.
-
- """
-
-def implementation_for(
- op_cls: Any, replace: bool = False
-) -> Callable[[_C], _C]:
- """Register an implementation for a given :class:`.MigrateOperation`.
-
- :param replace: when True, allows replacement of an already
- registered implementation for the given operation class. This
- enables customization of built-in operations such as
- :class:`.CreateTableOp` by providing an alternate implementation
- that can augment, modify, or conditionally invoke the default
- behavior.
-
- .. versionadded:: 1.17.2
-
- This is part of the operation extensibility API.
-
- .. seealso::
-
- :ref:`operation_plugins`
-
- :ref:`operations_extending_builtin`
-
- """
-
-def inline_literal(
- value: Union[str, int], type_: Optional[TypeEngine[Any]] = None
-) -> _literal_bindparam:
- r"""Produce an 'inline literal' expression, suitable for
- using in an INSERT, UPDATE, or DELETE statement.
-
- When using Alembic in "offline" mode, CRUD operations
- aren't compatible with SQLAlchemy's default behavior surrounding
- literal values,
- which is that they are converted into bound values and passed
- separately into the ``execute()`` method of the DBAPI cursor.
- An offline SQL
- script needs to have these rendered inline. While it should
- always be noted that inline literal values are an **enormous**
- security hole in an application that handles untrusted input,
- a schema migration is not run in this context, so
- literals are safe to render inline, with the caveat that
- advanced types like dates may not be supported directly
- by SQLAlchemy.
-
- See :meth:`.Operations.execute` for an example usage of
- :meth:`.Operations.inline_literal`.
-
- The environment can also be configured to attempt to render
- "literal" values inline automatically, for those simple types
- that are supported by the dialect; see
- :paramref:`.EnvironmentContext.configure.literal_binds` for this
- more recently added feature.
-
- :param value: The value to render. Strings, integers, and simple
- numerics should be supported. Other types like boolean,
- dates, etc. may or may not be supported yet by various
- backends.
- :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine`
- subclass stating the type of this value. In SQLAlchemy
- expressions, this is usually derived automatically
- from the Python type of the value itself, as well as
- based on the context in which the value is used.
-
- .. seealso::
-
- :paramref:`.EnvironmentContext.configure.literal_binds`
-
- """
-
-@overload
-def invoke(operation: CreateTableOp) -> Table: ...
-@overload
-def invoke(
- operation: Union[
- AddConstraintOp,
- DropConstraintOp,
- CreateIndexOp,
- DropIndexOp,
- AddColumnOp,
- AlterColumnOp,
- AlterTableOp,
- CreateTableCommentOp,
- DropTableCommentOp,
- DropColumnOp,
- BulkInsertOp,
- DropTableOp,
- ExecuteSQLOp,
- ],
-) -> None: ...
-@overload
-def invoke(operation: MigrateOperation) -> Any:
- """Given a :class:`.MigrateOperation`, invoke it in terms of
- this :class:`.Operations` instance.
-
- """
-
-def register_operation(
- name: str, sourcename: Optional[str] = None
-) -> Callable[[Type[_T]], Type[_T]]:
- """Register a new operation for this class.
-
- This method is normally used to add new operations
- to the :class:`.Operations` class, and possibly the
- :class:`.BatchOperations` class as well. All Alembic migration
- operations are implemented via this system, however the system
- is also available as a public API to facilitate adding custom
- operations.
-
- .. seealso::
-
- :ref:`operation_plugins`
-
-
- """
-
-def rename_table(
- old_table_name: str, new_table_name: str, *, schema: Optional[str] = None
-) -> None:
- """Emit an ALTER TABLE to rename a table.
-
- :param old_table_name: old name.
- :param new_table_name: new name.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
-
-def run_async(
- async_function: Callable[..., Awaitable[_T]], *args: Any, **kw_args: Any
-) -> _T:
- """Invoke the given asynchronous callable, passing an asynchronous
- :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first
- argument.
-
- This method allows calling async functions from within the
- synchronous ``upgrade()`` or ``downgrade()`` alembic migration
- method.
-
- The async connection passed to the callable shares the same
- transaction as the connection running in the migration context.
-
- Any additional arg or kw_arg passed to this function are passed
- to the provided async function.
-
- .. versionadded: 1.11
-
- .. note::
-
- This method can be called only when alembic is called using
- an async dialect.
- """
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__init__.py
deleted file mode 100644
index 26197cb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from . import toimpl
-from .base import AbstractOperations
-from .base import BatchOperations
-from .base import Operations
-from .ops import MigrateOperation
-from .ops import MigrationScript
-
-
-__all__ = [
- "AbstractOperations",
- "Operations",
- "BatchOperations",
- "MigrateOperation",
- "MigrationScript",
-]
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index d67a5d6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index 9601472..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc
deleted file mode 100644
index fc42399..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc
deleted file mode 100644
index 10011bc..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc
deleted file mode 100644
index 7f7fbe8..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc
deleted file mode 100644
index 51d4202..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/base.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/base.py
deleted file mode 100644
index 218ea19..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/base.py
+++ /dev/null
@@ -1,1955 +0,0 @@
-# mypy: allow-untyped-calls
-
-from __future__ import annotations
-
-from contextlib import contextmanager
-import re
-import textwrap
-from typing import Any
-from typing import Awaitable
-from typing import Callable
-from typing import Dict
-from typing import Iterator
-from typing import List # noqa
-from typing import Mapping
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Sequence # noqa
-from typing import Tuple
-from typing import Type # noqa
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy.sql.elements import conv
-
-from . import batch
-from . import schemaobj
-from .. import util
-from ..ddl.base import _ServerDefaultType
-from ..util import sqla_compat
-from ..util.compat import formatannotation_fwdref
-from ..util.compat import inspect_formatargspec
-from ..util.compat import inspect_getfullargspec
-from ..util.sqla_compat import _literal_bindparam
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy import Table
- from sqlalchemy.engine import Connection
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.expression import ColumnElement
- from sqlalchemy.sql.expression import TableClause
- from sqlalchemy.sql.expression import TextClause
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.types import TypeEngine
-
- from .batch import BatchOperationsImpl
- from .ops import AddColumnOp
- from .ops import AddConstraintOp
- from .ops import AlterColumnOp
- from .ops import AlterTableOp
- from .ops import BulkInsertOp
- from .ops import CreateIndexOp
- from .ops import CreateTableCommentOp
- from .ops import CreateTableOp
- from .ops import DropColumnOp
- from .ops import DropConstraintOp
- from .ops import DropIndexOp
- from .ops import DropTableCommentOp
- from .ops import DropTableOp
- from .ops import ExecuteSQLOp
- from .ops import MigrateOperation
- from ..ddl import DefaultImpl
- from ..runtime.migration import MigrationContext
-__all__ = ("Operations", "BatchOperations")
-_T = TypeVar("_T")
-
-_C = TypeVar("_C", bound=Callable[..., Any])
-
-
-class AbstractOperations(util.ModuleClsProxy):
- """Base class for Operations and BatchOperations.
-
- .. versionadded:: 1.11.0
-
- """
-
- impl: Union[DefaultImpl, BatchOperationsImpl]
- _to_impl = util.Dispatcher()
-
- def __init__(
- self,
- migration_context: MigrationContext,
- impl: Optional[BatchOperationsImpl] = None,
- ) -> None:
- """Construct a new :class:`.Operations`
-
- :param migration_context: a :class:`.MigrationContext`
- instance.
-
- """
- self.migration_context = migration_context
- if impl is None:
- self.impl = migration_context.impl
- else:
- self.impl = impl
-
- self.schema_obj = schemaobj.SchemaObjects(migration_context)
-
- @classmethod
- def register_operation(
- cls, name: str, sourcename: Optional[str] = None
- ) -> Callable[[Type[_T]], Type[_T]]:
- """Register a new operation for this class.
-
- This method is normally used to add new operations
- to the :class:`.Operations` class, and possibly the
- :class:`.BatchOperations` class as well. All Alembic migration
- operations are implemented via this system, however the system
- is also available as a public API to facilitate adding custom
- operations.
-
- .. seealso::
-
- :ref:`operation_plugins`
-
-
- """
-
- def register(op_cls: Type[_T]) -> Type[_T]:
- if sourcename is None:
- fn = getattr(op_cls, name)
- source_name = fn.__name__
- else:
- fn = getattr(op_cls, sourcename)
- source_name = fn.__name__
-
- spec = inspect_getfullargspec(fn)
-
- name_args = spec[0]
- assert name_args[0:2] == ["cls", "operations"]
-
- name_args[0:2] = ["self"]
-
- args = inspect_formatargspec(
- *spec, formatannotation=formatannotation_fwdref
- )
- num_defaults = len(spec[3]) if spec[3] else 0
-
- defaulted_vals: Tuple[Any, ...]
-
- if num_defaults:
- defaulted_vals = tuple(name_args[0 - num_defaults :])
- else:
- defaulted_vals = ()
-
- defaulted_vals += tuple(spec[4])
- # here, we are using formatargspec in a different way in order
- # to get a string that will re-apply incoming arguments to a new
- # function call
-
- apply_kw = inspect_formatargspec(
- name_args + spec[4],
- spec[1],
- spec[2],
- defaulted_vals,
- formatvalue=lambda x: "=" + x,
- formatannotation=formatannotation_fwdref,
- )
-
- args = re.sub(
- r'[_]?ForwardRef\(([\'"].+?[\'"])\)',
- lambda m: m.group(1),
- args,
- )
-
- func_text = textwrap.dedent(
- """\
- def %(name)s%(args)s:
- %(doc)r
- return op_cls.%(source_name)s%(apply_kw)s
- """
- % {
- "name": name,
- "source_name": source_name,
- "args": args,
- "apply_kw": apply_kw,
- "doc": fn.__doc__,
- }
- )
-
- globals_ = dict(globals())
- globals_.update({"op_cls": op_cls})
- lcl: Dict[str, Any] = {}
-
- exec(func_text, globals_, lcl)
- setattr(cls, name, lcl[name])
- fn.__func__.__doc__ = (
- "This method is proxied on "
- "the :class:`.%s` class, via the :meth:`.%s.%s` method."
- % (cls.__name__, cls.__name__, name)
- )
- if hasattr(fn, "_legacy_translations"):
- lcl[name]._legacy_translations = fn._legacy_translations
- return op_cls
-
- return register
-
- @classmethod
- def implementation_for(
- cls, op_cls: Any, replace: bool = False
- ) -> Callable[[_C], _C]:
- """Register an implementation for a given :class:`.MigrateOperation`.
-
- :param replace: when True, allows replacement of an already
- registered implementation for the given operation class. This
- enables customization of built-in operations such as
- :class:`.CreateTableOp` by providing an alternate implementation
- that can augment, modify, or conditionally invoke the default
- behavior.
-
- .. versionadded:: 1.17.2
-
- This is part of the operation extensibility API.
-
- .. seealso::
-
- :ref:`operation_plugins`
-
- :ref:`operations_extending_builtin`
-
- """
-
- def decorate(fn: _C) -> _C:
- cls._to_impl.dispatch_for(op_cls, replace=replace)(fn)
- return fn
-
- return decorate
-
- @classmethod
- @contextmanager
- def context(
- cls, migration_context: MigrationContext
- ) -> Iterator[Operations]:
- op = Operations(migration_context)
- op._install_proxy()
- yield op
- op._remove_proxy()
-
- @contextmanager
- def batch_alter_table(
- self,
- table_name: str,
- schema: Optional[str] = None,
- recreate: Literal["auto", "always", "never"] = "auto",
- partial_reordering: list[tuple[str, ...]] | None = None,
- copy_from: Optional[Table] = None,
- table_args: Tuple[Any, ...] = (),
- table_kwargs: Mapping[str, Any] = util.immutabledict(),
- reflect_args: Tuple[Any, ...] = (),
- reflect_kwargs: Mapping[str, Any] = util.immutabledict(),
- naming_convention: Optional[Dict[str, str]] = None,
- ) -> Iterator[BatchOperations]:
- """Invoke a series of per-table migrations in batch.
-
- Batch mode allows a series of operations specific to a table
- to be syntactically grouped together, and allows for alternate
- modes of table migration, in particular the "recreate" style of
- migration required by SQLite.
-
- "recreate" style is as follows:
-
- 1. A new table is created with the new specification, based on the
- migration directives within the batch, using a temporary name.
-
- 2. the data copied from the existing table to the new table.
-
- 3. the existing table is dropped.
-
- 4. the new table is renamed to the existing table name.
-
- The directive by default will only use "recreate" style on the
- SQLite backend, and only if directives are present which require
- this form, e.g. anything other than ``add_column()``. The batch
- operation on other backends will proceed using standard ALTER TABLE
- operations.
-
- The method is used as a context manager, which returns an instance
- of :class:`.BatchOperations`; this object is the same as
- :class:`.Operations` except that table names and schema names
- are omitted. E.g.::
-
- with op.batch_alter_table("some_table") as batch_op:
- batch_op.add_column(Column("foo", Integer))
- batch_op.drop_column("bar")
-
- The operations within the context manager are invoked at once
- when the context is ended. When run against SQLite, if the
- migrations include operations not supported by SQLite's ALTER TABLE,
- the entire table will be copied to a new one with the new
- specification, moving all data across as well.
-
- The copy operation by default uses reflection to retrieve the current
- structure of the table, and therefore :meth:`.batch_alter_table`
- in this mode requires that the migration is run in "online" mode.
- The ``copy_from`` parameter may be passed which refers to an existing
- :class:`.Table` object, which will bypass this reflection step.
-
- .. note:: The table copy operation will currently not copy
- CHECK constraints, and may not copy UNIQUE constraints that are
- unnamed, as is possible on SQLite. See the section
- :ref:`sqlite_batch_constraints` for workarounds.
-
- :param table_name: name of table
- :param schema: optional schema name.
- :param recreate: under what circumstances the table should be
- recreated. At its default of ``"auto"``, the SQLite dialect will
- recreate the table if any operations other than ``add_column()``,
- ``create_index()``, or ``drop_index()`` are
- present. Other options include ``"always"`` and ``"never"``.
- :param copy_from: optional :class:`~sqlalchemy.schema.Table` object
- that will act as the structure of the table being copied. If omitted,
- table reflection is used to retrieve the structure of the table.
-
- .. seealso::
-
- :ref:`batch_offline_mode`
-
- :paramref:`~.Operations.batch_alter_table.reflect_args`
-
- :paramref:`~.Operations.batch_alter_table.reflect_kwargs`
-
- :param reflect_args: a sequence of additional positional arguments that
- will be applied to the table structure being reflected / copied;
- this may be used to pass column and constraint overrides to the
- table that will be reflected, in lieu of passing the whole
- :class:`~sqlalchemy.schema.Table` using
- :paramref:`~.Operations.batch_alter_table.copy_from`.
- :param reflect_kwargs: a dictionary of additional keyword arguments
- that will be applied to the table structure being copied; this may be
- used to pass additional table and reflection options to the table that
- will be reflected, in lieu of passing the whole
- :class:`~sqlalchemy.schema.Table` using
- :paramref:`~.Operations.batch_alter_table.copy_from`.
- :param table_args: a sequence of additional positional arguments that
- will be applied to the new :class:`~sqlalchemy.schema.Table` when
- created, in addition to those copied from the source table.
- This may be used to provide additional constraints such as CHECK
- constraints that may not be reflected.
- :param table_kwargs: a dictionary of additional keyword arguments
- that will be applied to the new :class:`~sqlalchemy.schema.Table`
- when created, in addition to those copied from the source table.
- This may be used to provide for additional table options that may
- not be reflected.
- :param naming_convention: a naming convention dictionary of the form
- described at :ref:`autogen_naming_conventions` which will be applied
- to the :class:`~sqlalchemy.schema.MetaData` during the reflection
- process. This is typically required if one wants to drop SQLite
- constraints, as these constraints will not have names when
- reflected on this backend. Requires SQLAlchemy **0.9.4** or greater.
-
- .. seealso::
-
- :ref:`dropping_sqlite_foreign_keys`
-
- :param partial_reordering: a list of tuples, each suggesting a desired
- ordering of two or more columns in the newly created table. Requires
- that :paramref:`.batch_alter_table.recreate` is set to ``"always"``.
- Examples, given a table with columns "a", "b", "c", and "d":
-
- Specify the order of all columns::
-
- with op.batch_alter_table(
- "some_table",
- recreate="always",
- partial_reordering=[("c", "d", "a", "b")],
- ) as batch_op:
- pass
-
- Ensure "d" appears before "c", and "b", appears before "a"::
-
- with op.batch_alter_table(
- "some_table",
- recreate="always",
- partial_reordering=[("d", "c"), ("b", "a")],
- ) as batch_op:
- pass
-
- The ordering of columns not included in the partial_reordering
- set is undefined. Therefore it is best to specify the complete
- ordering of all columns for best results.
-
- .. note:: batch mode requires SQLAlchemy 0.8 or above.
-
- .. seealso::
-
- :ref:`batch_migrations`
-
- """
- impl = batch.BatchOperationsImpl(
- self,
- table_name,
- schema,
- recreate,
- copy_from,
- table_args,
- table_kwargs,
- reflect_args,
- reflect_kwargs,
- naming_convention,
- partial_reordering,
- )
- batch_op = BatchOperations(self.migration_context, impl=impl)
- yield batch_op
- impl.flush()
-
- def get_context(self) -> MigrationContext:
- """Return the :class:`.MigrationContext` object that's
- currently in use.
-
- """
-
- return self.migration_context
-
- @overload
- def invoke(self, operation: CreateTableOp) -> Table: ...
-
- @overload
- def invoke(
- self,
- operation: Union[
- AddConstraintOp,
- DropConstraintOp,
- CreateIndexOp,
- DropIndexOp,
- AddColumnOp,
- AlterColumnOp,
- AlterTableOp,
- CreateTableCommentOp,
- DropTableCommentOp,
- DropColumnOp,
- BulkInsertOp,
- DropTableOp,
- ExecuteSQLOp,
- ],
- ) -> None: ...
-
- @overload
- def invoke(self, operation: MigrateOperation) -> Any: ...
-
- def invoke(self, operation: MigrateOperation) -> Any:
- """Given a :class:`.MigrateOperation`, invoke it in terms of
- this :class:`.Operations` instance.
-
- """
- fn = self._to_impl.dispatch(
- operation, self.migration_context.impl.__dialect__
- )
- return fn(self, operation)
-
- def f(self, name: str) -> conv:
- """Indicate a string name that has already had a naming convention
- applied to it.
-
- This feature combines with the SQLAlchemy ``naming_convention`` feature
- to disambiguate constraint names that have already had naming
- conventions applied to them, versus those that have not. This is
- necessary in the case that the ``"%(constraint_name)s"`` token
- is used within a naming convention, so that it can be identified
- that this particular name should remain fixed.
-
- If the :meth:`.Operations.f` is used on a constraint, the naming
- convention will not take effect::
-
- op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x")))
-
- Above, the CHECK constraint generated will have the name
- ``ck_bool_t_x`` regardless of whether or not a naming convention is
- in use.
-
- Alternatively, if a naming convention is in use, and 'f' is not used,
- names will be converted along conventions. If the ``target_metadata``
- contains the naming convention
- ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the
- output of the following::
-
- op.add_column("t", "x", Boolean(name="x"))
-
- will be::
-
- CONSTRAINT ck_bool_t_x CHECK (x in (1, 0)))
-
- The function is rendered in the output of autogenerate when
- a particular constraint name is already converted.
-
- """
- return conv(name)
-
- def inline_literal(
- self, value: Union[str, int], type_: Optional[TypeEngine[Any]] = None
- ) -> _literal_bindparam:
- r"""Produce an 'inline literal' expression, suitable for
- using in an INSERT, UPDATE, or DELETE statement.
-
- When using Alembic in "offline" mode, CRUD operations
- aren't compatible with SQLAlchemy's default behavior surrounding
- literal values,
- which is that they are converted into bound values and passed
- separately into the ``execute()`` method of the DBAPI cursor.
- An offline SQL
- script needs to have these rendered inline. While it should
- always be noted that inline literal values are an **enormous**
- security hole in an application that handles untrusted input,
- a schema migration is not run in this context, so
- literals are safe to render inline, with the caveat that
- advanced types like dates may not be supported directly
- by SQLAlchemy.
-
- See :meth:`.Operations.execute` for an example usage of
- :meth:`.Operations.inline_literal`.
-
- The environment can also be configured to attempt to render
- "literal" values inline automatically, for those simple types
- that are supported by the dialect; see
- :paramref:`.EnvironmentContext.configure.literal_binds` for this
- more recently added feature.
-
- :param value: The value to render. Strings, integers, and simple
- numerics should be supported. Other types like boolean,
- dates, etc. may or may not be supported yet by various
- backends.
- :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine`
- subclass stating the type of this value. In SQLAlchemy
- expressions, this is usually derived automatically
- from the Python type of the value itself, as well as
- based on the context in which the value is used.
-
- .. seealso::
-
- :paramref:`.EnvironmentContext.configure.literal_binds`
-
- """
- return sqla_compat._literal_bindparam(None, value, type_=type_)
-
- def get_bind(self) -> Connection:
- """Return the current 'bind'.
-
- Under normal circumstances, this is the
- :class:`~sqlalchemy.engine.Connection` currently being used
- to emit SQL to the database.
-
- In a SQL script context, this value is ``None``. [TODO: verify this]
-
- """
- return self.migration_context.impl.bind # type: ignore[return-value]
-
- def run_async(
- self,
- async_function: Callable[..., Awaitable[_T]],
- *args: Any,
- **kw_args: Any,
- ) -> _T:
- """Invoke the given asynchronous callable, passing an asynchronous
- :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first
- argument.
-
- This method allows calling async functions from within the
- synchronous ``upgrade()`` or ``downgrade()`` alembic migration
- method.
-
- The async connection passed to the callable shares the same
- transaction as the connection running in the migration context.
-
- Any additional arg or kw_arg passed to this function are passed
- to the provided async function.
-
- .. versionadded: 1.11
-
- .. note::
-
- This method can be called only when alembic is called using
- an async dialect.
- """
- if not sqla_compat.sqla_14_18:
- raise NotImplementedError("SQLAlchemy 1.4.18+ required")
- sync_conn = self.get_bind()
- if sync_conn is None:
- raise NotImplementedError("Cannot call run_async in SQL mode")
- if not sync_conn.dialect.is_async:
- raise ValueError("Cannot call run_async with a sync engine")
- from sqlalchemy.ext.asyncio import AsyncConnection
- from sqlalchemy.util import await_only
-
- async_conn = AsyncConnection._retrieve_proxy_for_target(sync_conn)
- return await_only(async_function(async_conn, *args, **kw_args))
-
-
-class Operations(AbstractOperations):
- """Define high level migration operations.
-
- Each operation corresponds to some schema migration operation,
- executed against a particular :class:`.MigrationContext`
- which in turn represents connectivity to a database,
- or a file output stream.
-
- While :class:`.Operations` is normally configured as
- part of the :meth:`.EnvironmentContext.run_migrations`
- method called from an ``env.py`` script, a standalone
- :class:`.Operations` instance can be
- made for use cases external to regular Alembic
- migrations by passing in a :class:`.MigrationContext`::
-
- from alembic.migration import MigrationContext
- from alembic.operations import Operations
-
- conn = myengine.connect()
- ctx = MigrationContext.configure(conn)
- op = Operations(ctx)
-
- op.alter_column("t", "c", nullable=True)
-
- Note that as of 0.8, most of the methods on this class are produced
- dynamically using the :meth:`.Operations.register_operation`
- method.
-
- """
-
- if TYPE_CHECKING:
- # START STUB FUNCTIONS: op_cls
- # ### the following stubs are generated by tools/write_pyi.py ###
- # ### do not edit ###
-
- def add_column(
- self,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- """Issue an "add column" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy import Column, String
-
- op.add_column("organization", Column("name", String()))
-
- The :meth:`.Operations.add_column` method typically corresponds
- to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope
- of this command, the column's name, datatype, nullability,
- and optional server-generated defaults may be indicated.
-
- .. note::
-
- Not all contraint types may be indicated with this directive.
- PRIMARY KEY, NOT NULL, FOREIGN KEY, and CHECK are honored, UNIQUE
- is currently not.
-
- .. versionadded:: 1.18.2 Added support for PRIMARY KEY to be
- emitted within :meth:`.Operations.add_column`.
-
- As of 1.18.2, the following :class:`~sqlalchemy.schema.Column`
- parameters are **ignored**:
-
- * :paramref:`~sqlalchemy.schema.Column.unique` - use the
- :meth:`.Operations.create_unique_constraint` method
- * :paramref:`~sqlalchemy.schema.Column.index` - use the
- :meth:`.Operations.create_index` method
-
-
- The provided :class:`~sqlalchemy.schema.Column` object may include a
- :class:`~sqlalchemy.schema.ForeignKey` constraint directive,
- referencing a remote table name. By default, Alembic will automatically
- emit a second ALTER statement in order to add the single-column FOREIGN
- KEY constraint separately::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- )
-
- To render the FOREIGN KEY constraint inline within the ADD COLUMN
- directive, use the ``inline_references`` parameter. This can improve
- performance on large tables since the constraint is marked as valid
- immediately for nullable columns::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- inline_references=True,
- )
-
- The column argument passed to :meth:`.Operations.add_column` is a
- :class:`~sqlalchemy.schema.Column` construct, used in the same way it's
- used in SQLAlchemy. In particular, values or functions to be indicated
- as producing the column's default value on the database side are
- specified using the ``server_default`` parameter, and not ``default``
- which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the column add
- op.add_column(
- "account",
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- :param table_name: String name of the parent table.
- :param column: a :class:`sqlalchemy.schema.Column` object
- representing the new column.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator
- when creating the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param inline_references: If True, renders FOREIGN KEY constraints
- inline within the ADD COLUMN directive using REFERENCES syntax,
- rather than as a separate ALTER TABLE ADD CONSTRAINT statement.
- This is supported by PostgreSQL, Oracle, MySQL 5.7+, and
- MariaDB 10.5+.
-
- .. versionadded:: 1.18.2
-
- """ # noqa: E501
- ...
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- comment: Union[str, Literal[False], None] = False,
- server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- new_column_name: Optional[str] = None,
- type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
- existing_type: Union[
- TypeEngine[Any], Type[TypeEngine[Any]], None
- ] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- r"""Issue an "alter column" instruction using the
- current migration context.
-
- Generally, only that aspect of the column which
- is being changed, i.e. name, type, nullability,
- default, needs to be specified. Multiple changes
- can also be specified at once and the backend should
- "do the right thing", emitting each change either
- separately or together as the backend allows.
-
- MySQL has special requirements here, since MySQL
- cannot ALTER a column without a full specification.
- When producing MySQL-compatible migration files,
- it is recommended that the ``existing_type``,
- ``existing_server_default``, and ``existing_nullable``
- parameters be present, if not being altered.
-
- Type changes which are against the SQLAlchemy
- "schema" types :class:`~sqlalchemy.types.Boolean`
- and :class:`~sqlalchemy.types.Enum` may also
- add or drop constraints which accompany those
- types on backends that don't support them natively.
- The ``existing_type`` argument is
- used in this case to identify and remove a previous
- constraint that was bound to the type object.
-
- :param table_name: string name of the target table.
- :param column_name: string name of the target column,
- as it exists before the operation begins.
- :param nullable: Optional; specify ``True`` or ``False``
- to alter the column's nullability.
- :param server_default: Optional; specify a string
- SQL expression, :func:`~sqlalchemy.sql.expression.text`,
- or :class:`~sqlalchemy.schema.DefaultClause` to indicate
- an alteration to the column's default value.
- Set to ``None`` to have the default removed.
- :param comment: optional string text of a new comment to add to the
- column.
- :param new_column_name: Optional; specify a string name here to
- indicate the new name within a column rename operation.
- :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
- type object to specify a change to the column's type.
- For SQLAlchemy types that also indicate a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
- the constraint is also generated.
- :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column;
- currently understood by the MySQL dialect.
- :param existing_type: Optional; a
- :class:`~sqlalchemy.types.TypeEngine`
- type object to specify the previous type. This
- is required for all MySQL column alter operations that
- don't otherwise specify a new type, as well as for
- when nullability is being changed on a SQL Server
- column. It is also used if the type is a so-called
- SQLAlchemy "schema" type which may define a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`,
- :class:`~sqlalchemy.types.Enum`),
- so that the constraint can be dropped.
- :param existing_server_default: Optional; The existing
- default value of the column. Required on MySQL if
- an existing default is not being changed; else MySQL
- removes the default.
- :param existing_nullable: Optional; the existing nullability
- of the column. Required on MySQL if the existing nullability
- is not being changed; else MySQL sets this to NULL.
- :param existing_autoincrement: Optional; the existing autoincrement
- of the column. Used for MySQL's system of altering a column
- that specifies ``AUTO_INCREMENT``.
- :param existing_comment: string text of the existing comment on the
- column to be maintained. Required on MySQL if the existing comment
- on the column is not being changed.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param postgresql_using: String argument which will indicate a
- SQL expression to render within the Postgresql-specific USING clause
- within ALTER COLUMN. This string is taken directly as raw SQL which
- must explicitly include any necessary quoting or escaping of tokens
- within the expression.
-
- """ # noqa: E501
- ...
-
- def bulk_insert(
- self,
- table: Union[Table, TableClause],
- rows: List[Dict[str, Any]],
- *,
- multiinsert: bool = True,
- ) -> None:
- """Issue a "bulk insert" operation using the current
- migration context.
-
- This provides a means of representing an INSERT of multiple rows
- which works equally well in the context of executing on a live
- connection as well as that of generating a SQL script. In the
- case of a SQL script, the values are rendered inline into the
- statement.
-
- e.g.::
-
- from alembic import op
- from datetime import date
- from sqlalchemy.sql import table, column
- from sqlalchemy import String, Integer, Date
-
- # Create an ad-hoc table to use for the insert statement.
- accounts_table = table(
- "account",
- column("id", Integer),
- column("name", String),
- column("create_date", Date),
- )
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": date(2010, 10, 5),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": date(2007, 5, 27),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": date(2008, 8, 15),
- },
- ],
- )
-
- When using --sql mode, some datatypes may not render inline
- automatically, such as dates and other special types. When this
- issue is present, :meth:`.Operations.inline_literal` may be used::
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": op.inline_literal("2010-10-05"),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": op.inline_literal("2007-05-27"),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": op.inline_literal("2008-08-15"),
- },
- ],
- multiinsert=False,
- )
-
- When using :meth:`.Operations.inline_literal` in conjunction with
- :meth:`.Operations.bulk_insert`, in order for the statement to work
- in "online" (e.g. non --sql) mode, the
- :paramref:`~.Operations.bulk_insert.multiinsert`
- flag should be set to ``False``, which will have the effect of
- individual INSERT statements being emitted to the database, each
- with a distinct VALUES clause, so that the "inline" values can
- still be rendered, rather than attempting to pass the values
- as bound parameters.
-
- :param table: a table object which represents the target of the INSERT.
-
- :param rows: a list of dictionaries indicating rows.
-
- :param multiinsert: when at its default of True and --sql mode is not
- enabled, the INSERT statement will be executed using
- "executemany()" style, where all elements in the list of
- dictionaries are passed as bound parameters in a single
- list. Setting this to False results in individual INSERT
- statements being emitted per parameter set, and is needed
- in those cases where non-literal values are present in the
- parameter sets.
-
- """ # noqa: E501
- ...
-
- def create_check_constraint(
- self,
- constraint_name: Optional[str],
- table_name: str,
- condition: Union[str, ColumnElement[bool], TextClause],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue a "create check constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy.sql import column, func
-
- op.create_check_constraint(
- "ck_user_name_len",
- "user",
- func.len(column("name")) > 5,
- )
-
- CHECK constraints are usually against a SQL expression, so ad-hoc
- table metadata is usually needed. The function will convert the given
- arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound
- to an anonymous table in order to emit the CREATE statement.
-
- :param name: Name of the check constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param condition: SQL expression that's the condition of the
- constraint. Can be a string or SQLAlchemy expression language
- structure.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """ # noqa: E501
- ...
-
- def create_exclude_constraint(
- self,
- constraint_name: str,
- table_name: str,
- *elements: Any,
- **kw: Any,
- ) -> Optional[Table]:
- """Issue an alter to create an EXCLUDE constraint using the
- current migration context.
-
- .. note:: This method is Postgresql specific, and additionally
- requires at least SQLAlchemy 1.0.
-
- e.g.::
-
- from alembic import op
-
- op.create_exclude_constraint(
- "user_excl",
- "user",
- ("period", "&&"),
- ("group", "="),
- where=("group != 'some group'"),
- )
-
- Note that the expressions work the same way as that of
- the ``ExcludeConstraint`` object itself; if plain strings are
- passed, quoting rules must be applied manually.
-
- :param name: Name of the constraint.
- :param table_name: String name of the source table.
- :param elements: exclude conditions.
- :param where: SQL expression or SQL string with optional WHERE
- clause.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within.
-
- """ # noqa: E501
- ...
-
- def create_foreign_key(
- self,
- constraint_name: Optional[str],
- source_table: str,
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- *,
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- source_schema: Optional[str] = None,
- referent_schema: Optional[str] = None,
- **dialect_kw: Any,
- ) -> None:
- """Issue a "create foreign key" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_foreign_key(
- "fk_user_address",
- "address",
- "user",
- ["user_id"],
- ["id"],
- )
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.ForeignKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the foreign key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param source_table: String name of the source table.
- :param referent_table: String name of the destination table.
- :param local_cols: a list of string column names in the
- source table.
- :param remote_cols: a list of string column names in the
- remote table.
- :param onupdate: Optional string. If set, emit ON UPDATE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param ondelete: Optional string. If set, emit ON DELETE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param deferrable: optional bool. If set, emit DEFERRABLE or NOT
- DEFERRABLE when issuing DDL for this constraint.
- :param source_schema: Optional schema name of the source table.
- :param referent_schema: Optional schema name of the destination table.
-
- """ # noqa: E501
- ...
-
- def create_index(
- self,
- index_name: Optional[str],
- table_name: str,
- columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
- *,
- schema: Optional[str] = None,
- unique: bool = False,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "create index" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_index("ik_test", "t1", ["foo", "bar"])
-
- Functional indexes can be produced by using the
- :func:`sqlalchemy.sql.expression.text` construct::
-
- from alembic import op
- from sqlalchemy import text
-
- op.create_index("ik_test", "t1", [text("lower(foo)")])
-
- :param index_name: name of the index.
- :param table_name: name of the owning table.
- :param columns: a list consisting of string column names and/or
- :func:`~sqlalchemy.sql.expression.text` constructs.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param unique: If True, create a unique index.
-
- :param quote: Force quoting of this column's name on or off,
- corresponding to ``True`` or ``False``. When left at its default
- of ``None``, the column identifier will be quoted according to
- whether the name is case sensitive (identifiers with at least one
- upper case character are treated as case sensitive), or if it's a
- reserved word. This flag is only needed to force quoting of a
- reserved word which is not known by the SQLAlchemy dialect.
-
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """ # noqa: E501
- ...
-
- def create_primary_key(
- self,
- constraint_name: Optional[str],
- table_name: str,
- columns: List[str],
- *,
- schema: Optional[str] = None,
- ) -> None:
- """Issue a "create primary key" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_primary_key("pk_my_table", "my_table", ["id", "version"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.PrimaryKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the primary key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the target table.
- :param columns: a list of string column names to be applied to the
- primary key constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """ # noqa: E501
- ...
-
- def create_table(
- self,
- table_name: str,
- *columns: SchemaItem,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
- ) -> Table:
- r"""Issue a "create table" instruction using the current migration
- context.
-
- This directive receives an argument list similar to that of the
- traditional :class:`sqlalchemy.schema.Table` construct, but without the
- metadata::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- Note that :meth:`.create_table` accepts
- :class:`~sqlalchemy.schema.Column`
- constructs directly from the SQLAlchemy library. In particular,
- default values to be created on the database side are
- specified using the ``server_default`` parameter, and not
- ``default`` which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the "timestamp" column
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- The function also returns a newly created
- :class:`~sqlalchemy.schema.Table` object, corresponding to the table
- specification given, which is suitable for
- immediate SQL operations, in particular
- :meth:`.Operations.bulk_insert`::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- account_table = op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- op.bulk_insert(
- account_table,
- [
- {"name": "A1", "description": "account 1"},
- {"name": "A2", "description": "account 2"},
- ],
- )
-
- :param table_name: Name of the table
- :param \*columns: collection of :class:`~sqlalchemy.schema.Column`
- objects within
- the table, as well as optional :class:`~sqlalchemy.schema.Constraint`
- objects
- and :class:`~.sqlalchemy.schema.Index` objects.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- :return: the :class:`~sqlalchemy.schema.Table` object corresponding
- to the parameters given.
-
- """ # noqa: E501
- ...
-
- def create_table_comment(
- self,
- table_name: str,
- comment: Optional[str],
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- ) -> None:
- """Emit a COMMENT ON operation to set the comment for a table.
-
- :param table_name: string name of the target table.
- :param comment: string value of the comment being registered against
- the specified table.
- :param existing_comment: String value of a comment
- already registered on the specified table, used within autogenerate
- so that the operation is reversible, but not required for direct
- use.
-
- .. seealso::
-
- :meth:`.Operations.drop_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """ # noqa: E501
- ...
-
- def create_unique_constraint(
- self,
- constraint_name: Optional[str],
- table_name: str,
- columns: Sequence[str],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> Any:
- """Issue a "create unique constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- op.create_unique_constraint("uq_user_name", "user", ["name"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.UniqueConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param name: Name of the unique constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param columns: a list of string column names in the
- source table.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """ # noqa: E501
- ...
-
- def drop_column(
- self,
- table_name: str,
- column_name: str,
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue a "drop column" instruction using the current
- migration context.
-
- e.g.::
-
- drop_column("organization", "account_id")
-
- :param table_name: name of table
- :param column_name: name of column
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param mssql_drop_check: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the CHECK constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.check_constraints,
- then exec's a separate DROP CONSTRAINT for that constraint.
- :param mssql_drop_default: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the DEFAULT constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.default_constraints,
- then exec's a separate DROP CONSTRAINT for that default.
- :param mssql_drop_foreign_key: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop a single FOREIGN KEY constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from
- sys.foreign_keys/sys.foreign_key_columns,
- then exec's a separate DROP CONSTRAINT for that default. Only
- works if the column has exactly one FK constraint which refers to
- it, at the moment.
- """ # noqa: E501
- ...
-
- def drop_constraint(
- self,
- constraint_name: str,
- table_name: str,
- type_: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- ) -> None:
- r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
-
- :param constraint_name: name of the constraint.
- :param table_name: table name.
- :param type\_: optional, required on MySQL. can be
- 'foreignkey', 'primary', 'unique', or 'check'.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the constraint
-
- .. versionadded:: 1.16.0
-
- """ # noqa: E501
- ...
-
- def drop_index(
- self,
- index_name: str,
- table_name: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "drop index" instruction using the current
- migration context.
-
- e.g.::
-
- drop_index("accounts")
-
- :param index_name: name of the index.
- :param table_name: name of the owning table. Some
- backends such as Microsoft SQL Server require this.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """ # noqa: E501
- ...
-
- def drop_table(
- self,
- table_name: str,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "drop table" instruction using the current
- migration context.
-
-
- e.g.::
-
- drop_table("accounts")
-
- :param table_name: Name of the table
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- """ # noqa: E501
- ...
-
- def drop_table_comment(
- self,
- table_name: str,
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- ) -> None:
- """Issue a "drop table comment" operation to
- remove an existing comment set on a table.
-
- :param table_name: string name of the target table.
- :param existing_comment: An optional string value of a comment already
- registered on the specified table.
-
- .. seealso::
-
- :meth:`.Operations.create_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """ # noqa: E501
- ...
-
- def execute(
- self,
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- r"""Execute the given SQL using the current migration context.
-
- The given SQL can be a plain string, e.g.::
-
- op.execute("INSERT INTO table (foo) VALUES ('some value')")
-
- Or it can be any kind of Core SQL Expression construct, such as
- below where we use an update construct::
-
- from sqlalchemy.sql import table, column
- from sqlalchemy import String
- from alembic import op
-
- account = table("account", column("name", String))
- op.execute(
- account.update()
- .where(account.c.name == op.inline_literal("account 1"))
- .values({"name": op.inline_literal("account 2")})
- )
-
- Above, we made use of the SQLAlchemy
- :func:`sqlalchemy.sql.expression.table` and
- :func:`sqlalchemy.sql.expression.column` constructs to make a brief,
- ad-hoc table construct just for our UPDATE statement. A full
- :class:`~sqlalchemy.schema.Table` construct of course works perfectly
- fine as well, though note it's a recommended practice to at least
- ensure the definition of a table is self-contained within the migration
- script, rather than imported from a module that may break compatibility
- with older migrations.
-
- In a SQL script context, the statement is emitted directly to the
- output stream. There is *no* return result, however, as this
- function is oriented towards generating a change script
- that can run in "offline" mode. Additionally, parameterized
- statements are discouraged here, as they *will not work* in offline
- mode. Above, we use :meth:`.inline_literal` where parameters are
- to be used.
-
- For full interaction with a connected database where parameters can
- also be used normally, use the "bind" available from the context::
-
- from alembic import op
-
- connection = op.get_bind()
-
- connection.execute(
- account.update()
- .where(account.c.name == "account 1")
- .values({"name": "account 2"})
- )
-
- Additionally, when passing the statement as a plain string, it is first
- coerced into a :func:`sqlalchemy.sql.expression.text` construct
- before being passed along. In the less likely case that the
- literal SQL string contains a colon, it must be escaped with a
- backslash, as::
-
- op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')")
-
-
- :param sqltext: Any legal SQLAlchemy expression, including:
-
- * a string
- * a :func:`sqlalchemy.sql.expression.text` construct.
- * a :func:`sqlalchemy.sql.expression.insert` construct.
- * a :func:`sqlalchemy.sql.expression.update` construct.
- * a :func:`sqlalchemy.sql.expression.delete` construct.
- * Any "executable" described in SQLAlchemy Core documentation,
- noting that no result set is returned.
-
- .. note:: when passing a plain string, the statement is coerced into
- a :func:`sqlalchemy.sql.expression.text` construct. This construct
- considers symbols with colons, e.g. ``:foo`` to be bound parameters.
- To avoid this, ensure that colon symbols are escaped, e.g.
- ``\:foo``.
-
- :param execution_options: Optional dictionary of
- execution options, will be passed to
- :meth:`sqlalchemy.engine.Connection.execution_options`.
- """ # noqa: E501
- ...
-
- def rename_table(
- self,
- old_table_name: str,
- new_table_name: str,
- *,
- schema: Optional[str] = None,
- ) -> None:
- """Emit an ALTER TABLE to rename a table.
-
- :param old_table_name: old name.
- :param new_table_name: new name.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """ # noqa: E501
- ...
-
- # END STUB FUNCTIONS: op_cls
-
-
-class BatchOperations(AbstractOperations):
- """Modifies the interface :class:`.Operations` for batch mode.
-
- This basically omits the ``table_name`` and ``schema`` parameters
- from associated methods, as these are a given when running under batch
- mode.
-
- .. seealso::
-
- :meth:`.Operations.batch_alter_table`
-
- Note that as of 0.8, most of the methods on this class are produced
- dynamically using the :meth:`.Operations.register_operation`
- method.
-
- """
-
- impl: BatchOperationsImpl
-
- def _noop(self, operation: Any) -> NoReturn:
- raise NotImplementedError(
- "The %s method does not apply to a batch table alter operation."
- % operation
- )
-
- if TYPE_CHECKING:
- # START STUB FUNCTIONS: batch_op
- # ### the following stubs are generated by tools/write_pyi.py ###
- # ### do not edit ###
-
- def add_column(
- self,
- column: Column[Any],
- *,
- insert_before: Optional[str] = None,
- insert_after: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- """Issue an "add column" instruction using the current
- batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.add_column`
-
- """ # noqa: E501
- ...
-
- def alter_column(
- self,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- comment: Union[str, Literal[False], None] = False,
- server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- new_column_name: Optional[str] = None,
- type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
- existing_type: Union[
- TypeEngine[Any], Type[TypeEngine[Any]], None
- ] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- insert_before: Optional[str] = None,
- insert_after: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue an "alter column" instruction using the current
- batch migration context.
-
- Parameters are the same as that of :meth:`.Operations.alter_column`,
- as well as the following option(s):
-
- :param insert_before: String name of an existing column which this
- column should be placed before, when creating the new table.
-
- :param insert_after: String name of an existing column which this
- column should be placed after, when creating the new table. If
- both :paramref:`.BatchOperations.alter_column.insert_before`
- and :paramref:`.BatchOperations.alter_column.insert_after` are
- omitted, the column is inserted after the last existing column
- in the table.
-
- .. seealso::
-
- :meth:`.Operations.alter_column`
-
-
- """ # noqa: E501
- ...
-
- def create_check_constraint(
- self,
- constraint_name: str,
- condition: Union[str, ColumnElement[bool], TextClause],
- **kw: Any,
- ) -> None:
- """Issue a "create check constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_check_constraint`
-
- """ # noqa: E501
- ...
-
- def create_exclude_constraint(
- self, constraint_name: str, *elements: Any, **kw: Any
- ) -> Optional[Table]:
- """Issue a "create exclude constraint" instruction using the
- current batch migration context.
-
- .. note:: This method is Postgresql specific, and additionally
- requires at least SQLAlchemy 1.0.
-
- .. seealso::
-
- :meth:`.Operations.create_exclude_constraint`
-
- """ # noqa: E501
- ...
-
- def create_foreign_key(
- self,
- constraint_name: Optional[str],
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- *,
- referent_schema: Optional[str] = None,
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- **dialect_kw: Any,
- ) -> None:
- """Issue a "create foreign key" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``source_schema``
- arguments from the call.
-
- e.g.::
-
- with batch_alter_table("address") as batch_op:
- batch_op.create_foreign_key(
- "fk_user_address",
- "user",
- ["user_id"],
- ["id"],
- )
-
- .. seealso::
-
- :meth:`.Operations.create_foreign_key`
-
- """ # noqa: E501
- ...
-
- def create_index(
- self, index_name: str, columns: List[str], **kw: Any
- ) -> None:
- """Issue a "create index" instruction using the
- current batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.create_index`
-
- """ # noqa: E501
- ...
-
- def create_primary_key(
- self, constraint_name: Optional[str], columns: List[str]
- ) -> None:
- """Issue a "create primary key" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``table_name`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_primary_key`
-
- """ # noqa: E501
- ...
-
- def create_table_comment(
- self,
- comment: Optional[str],
- *,
- existing_comment: Optional[str] = None,
- ) -> None:
- """Emit a COMMENT ON operation to set the comment for a table
- using the current batch migration context.
-
- :param comment: string value of the comment being registered against
- the specified table.
- :param existing_comment: String value of a comment
- already registered on the specified table, used within autogenerate
- so that the operation is reversible, but not required for direct
- use.
-
- """ # noqa: E501
- ...
-
- def create_unique_constraint(
- self, constraint_name: str, columns: Sequence[str], **kw: Any
- ) -> Any:
- """Issue a "create unique constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_unique_constraint`
-
- """ # noqa: E501
- ...
-
- def drop_column(self, column_name: str, **kw: Any) -> None:
- """Issue a "drop column" instruction using the current
- batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.drop_column`
-
- """ # noqa: E501
- ...
-
- def drop_constraint(
- self, constraint_name: str, type_: Optional[str] = None
- ) -> None:
- """Issue a "drop constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``table_name`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.drop_constraint`
-
- """ # noqa: E501
- ...
-
- def drop_index(self, index_name: str, **kw: Any) -> None:
- """Issue a "drop index" instruction using the
- current batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.drop_index`
-
- """ # noqa: E501
- ...
-
- def drop_table_comment(
- self, *, existing_comment: Optional[str] = None
- ) -> None:
- """Issue a "drop table comment" operation to
- remove an existing comment set on a table using the current
- batch operations context.
-
- :param existing_comment: An optional string value of a comment already
- registered on the specified table.
-
- """ # noqa: E501
- ...
-
- def execute(
- self,
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- """Execute the given SQL using the current migration context.
-
- .. seealso::
-
- :meth:`.Operations.execute`
-
- """ # noqa: E501
- ...
-
- # END STUB FUNCTIONS: batch_op
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/batch.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/batch.py
deleted file mode 100644
index 9b48be5..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/batch.py
+++ /dev/null
@@ -1,720 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import CheckConstraint
-from sqlalchemy import Column
-from sqlalchemy import ForeignKeyConstraint
-from sqlalchemy import Index
-from sqlalchemy import MetaData
-from sqlalchemy import PrimaryKeyConstraint
-from sqlalchemy import schema as sql_schema
-from sqlalchemy import select
-from sqlalchemy import Table
-from sqlalchemy import types as sqltypes
-from sqlalchemy.sql.schema import SchemaEventTarget
-from sqlalchemy.util import OrderedDict
-from sqlalchemy.util import topological
-
-from ..util import exc
-from ..util.sqla_compat import _columns_for_constraint
-from ..util.sqla_compat import _copy
-from ..util.sqla_compat import _copy_expression
-from ..util.sqla_compat import _ensure_scope_for_ddl
-from ..util.sqla_compat import _fk_is_self_referential
-from ..util.sqla_compat import _idx_table_bound_expressions
-from ..util.sqla_compat import _is_type_bound
-from ..util.sqla_compat import _remove_column_from_collection
-from ..util.sqla_compat import _resolve_for_variant
-from ..util.sqla_compat import constraint_name_defined
-from ..util.sqla_compat import constraint_name_string
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy.engine import Dialect
- from sqlalchemy.sql.elements import ColumnClause
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.type_api import TypeEngine
-
- from ..ddl.base import _ServerDefaultType
- from ..ddl.impl import DefaultImpl
-
-
-class BatchOperationsImpl:
- def __init__(
- self,
- operations,
- table_name,
- schema,
- recreate,
- copy_from,
- table_args,
- table_kwargs,
- reflect_args,
- reflect_kwargs,
- naming_convention,
- partial_reordering,
- ):
- self.operations = operations
- self.table_name = table_name
- self.schema = schema
- if recreate not in ("auto", "always", "never"):
- raise ValueError(
- "recreate may be one of 'auto', 'always', or 'never'."
- )
- self.recreate = recreate
- self.copy_from = copy_from
- self.table_args = table_args
- self.table_kwargs = dict(table_kwargs)
- self.reflect_args = reflect_args
- self.reflect_kwargs = dict(reflect_kwargs)
- self.reflect_kwargs.setdefault(
- "listeners", list(self.reflect_kwargs.get("listeners", ()))
- )
- self.reflect_kwargs["listeners"].append(
- ("column_reflect", operations.impl.autogen_column_reflect)
- )
- self.naming_convention = naming_convention
- self.partial_reordering = partial_reordering
- self.batch = []
-
- @property
- def dialect(self) -> Dialect:
- return self.operations.impl.dialect
-
- @property
- def impl(self) -> DefaultImpl:
- return self.operations.impl
-
- def _should_recreate(self) -> bool:
- if self.recreate == "auto":
- return self.operations.impl.requires_recreate_in_batch(self)
- elif self.recreate == "always":
- return True
- else:
- return False
-
- def flush(self) -> None:
- should_recreate = self._should_recreate()
-
- with _ensure_scope_for_ddl(self.impl.connection):
- if not should_recreate:
- for opname, arg, kw in self.batch:
- fn = getattr(self.operations.impl, opname)
- fn(*arg, **kw)
- else:
- if self.naming_convention:
- m1 = MetaData(naming_convention=self.naming_convention)
- else:
- m1 = MetaData()
-
- if self.copy_from is not None:
- existing_table = self.copy_from
- reflected = False
- else:
- if self.operations.migration_context.as_sql:
- raise exc.CommandError(
- f"This operation cannot proceed in --sql mode; "
- f"batch mode with dialect "
- f"{self.operations.migration_context.dialect.name} " # noqa: E501
- f"requires a live database connection with which "
- f'to reflect the table "{self.table_name}". '
- f"To generate a batch SQL migration script using "
- "table "
- '"move and copy", a complete Table object '
- f'should be passed to the "copy_from" argument '
- "of the batch_alter_table() method so that table "
- "reflection can be skipped."
- )
-
- existing_table = Table(
- self.table_name,
- m1,
- schema=self.schema,
- autoload_with=self.operations.get_bind(),
- *self.reflect_args,
- **self.reflect_kwargs,
- )
- reflected = True
-
- batch_impl = ApplyBatchImpl(
- self.impl,
- existing_table,
- self.table_args,
- self.table_kwargs,
- reflected,
- partial_reordering=self.partial_reordering,
- )
- for opname, arg, kw in self.batch:
- fn = getattr(batch_impl, opname)
- fn(*arg, **kw)
-
- batch_impl._create(self.impl)
-
- def alter_column(self, *arg, **kw) -> None:
- self.batch.append(("alter_column", arg, kw))
-
- def add_column(self, *arg, **kw) -> None:
- if (
- "insert_before" in kw or "insert_after" in kw
- ) and not self._should_recreate():
- raise exc.CommandError(
- "Can't specify insert_before or insert_after when using "
- "ALTER; please specify recreate='always'"
- )
- self.batch.append(("add_column", arg, kw))
-
- def drop_column(self, *arg, **kw) -> None:
- self.batch.append(("drop_column", arg, kw))
-
- def add_constraint(self, const: Constraint) -> None:
- self.batch.append(("add_constraint", (const,), {}))
-
- def drop_constraint(self, const: Constraint) -> None:
- self.batch.append(("drop_constraint", (const,), {}))
-
- def rename_table(self, *arg, **kw):
- self.batch.append(("rename_table", arg, kw))
-
- def create_index(self, idx: Index, **kw: Any) -> None:
- self.batch.append(("create_index", (idx,), kw))
-
- def drop_index(self, idx: Index, **kw: Any) -> None:
- self.batch.append(("drop_index", (idx,), kw))
-
- def create_table_comment(self, table):
- self.batch.append(("create_table_comment", (table,), {}))
-
- def drop_table_comment(self, table):
- self.batch.append(("drop_table_comment", (table,), {}))
-
- def create_table(self, table):
- raise NotImplementedError("Can't create table in batch mode")
-
- def drop_table(self, table):
- raise NotImplementedError("Can't drop table in batch mode")
-
- def create_column_comment(self, column):
- self.batch.append(("create_column_comment", (column,), {}))
-
-
-class ApplyBatchImpl:
- def __init__(
- self,
- impl: DefaultImpl,
- table: Table,
- table_args: tuple,
- table_kwargs: Dict[str, Any],
- reflected: bool,
- partial_reordering: tuple = (),
- ) -> None:
- self.impl = impl
- self.table = table # this is a Table object
- self.table_args = table_args
- self.table_kwargs = table_kwargs
- self.temp_table_name = self._calc_temp_name(table.name)
- self.new_table: Optional[Table] = None
-
- self.partial_reordering = partial_reordering # tuple of tuples
- self.add_col_ordering: Tuple[
- Tuple[str, str], ...
- ] = () # tuple of tuples
-
- self.column_transfers = OrderedDict(
- (c.name, {"expr": c}) for c in self.table.c
- )
- self.existing_ordering = list(self.column_transfers)
-
- self.reflected = reflected
- self._grab_table_elements()
-
- @classmethod
- def _calc_temp_name(cls, tablename: Union[quoted_name, str]) -> str:
- return ("_alembic_tmp_%s" % tablename)[0:50]
-
- def _grab_table_elements(self) -> None:
- schema = self.table.schema
- self.columns: Dict[str, Column[Any]] = OrderedDict()
- for c in self.table.c:
- c_copy = _copy(c, schema=schema)
- c_copy.unique = c_copy.index = False
- # ensure that the type object was copied,
- # as we may need to modify it in-place
- if isinstance(c.type, SchemaEventTarget):
- assert c_copy.type is not c.type
- self.columns[c.name] = c_copy
- self.named_constraints: Dict[str, Constraint] = {}
- self.unnamed_constraints = []
- self.col_named_constraints = {}
- self.indexes: Dict[str, Index] = {}
- self.new_indexes: Dict[str, Index] = {}
-
- for const in self.table.constraints:
- if _is_type_bound(const):
- continue
- elif (
- self.reflected
- and isinstance(const, CheckConstraint)
- and not const.name
- ):
- # TODO: we are skipping unnamed reflected CheckConstraint
- # because
- # we have no way to determine _is_type_bound() for these.
- pass
- elif constraint_name_string(const.name):
- self.named_constraints[const.name] = const
- else:
- self.unnamed_constraints.append(const)
-
- if not self.reflected:
- for col in self.table.c:
- for const in col.constraints:
- if const.name:
- self.col_named_constraints[const.name] = (col, const)
-
- for idx in self.table.indexes:
- self.indexes[idx.name] = idx # type: ignore[index]
-
- for k in self.table.kwargs:
- self.table_kwargs.setdefault(k, self.table.kwargs[k])
-
- def _adjust_self_columns_for_partial_reordering(self) -> None:
- pairs = set()
-
- col_by_idx = list(self.columns)
-
- if self.partial_reordering:
- for tuple_ in self.partial_reordering:
- for index, elem in enumerate(tuple_):
- if index > 0:
- pairs.add((tuple_[index - 1], elem))
- else:
- for index, elem in enumerate(self.existing_ordering):
- if index > 0:
- pairs.add((col_by_idx[index - 1], elem))
-
- pairs.update(self.add_col_ordering)
-
- # this can happen if some columns were dropped and not removed
- # from existing_ordering. this should be prevented already, but
- # conservatively making sure this didn't happen
- pairs_list = [p for p in pairs if p[0] != p[1]]
-
- sorted_ = list(
- topological.sort(pairs_list, col_by_idx, deterministic_order=True)
- )
- self.columns = OrderedDict((k, self.columns[k]) for k in sorted_)
- self.column_transfers = OrderedDict(
- (k, self.column_transfers[k]) for k in sorted_
- )
-
- def _transfer_elements_to_new_table(self) -> None:
- assert self.new_table is None, "Can only create new table once"
-
- m = MetaData()
- schema = self.table.schema
-
- if self.partial_reordering or self.add_col_ordering:
- self._adjust_self_columns_for_partial_reordering()
-
- self.new_table = new_table = Table(
- self.temp_table_name,
- m,
- *(list(self.columns.values()) + list(self.table_args)),
- schema=schema,
- **self.table_kwargs,
- )
-
- for const in (
- list(self.named_constraints.values()) + self.unnamed_constraints
- ):
- const_columns = {c.key for c in _columns_for_constraint(const)}
-
- if not const_columns.issubset(self.column_transfers):
- continue
-
- const_copy: Constraint
- if isinstance(const, ForeignKeyConstraint):
- if _fk_is_self_referential(const):
- # for self-referential constraint, refer to the
- # *original* table name, and not _alembic_batch_temp.
- # This is consistent with how we're handling
- # FK constraints from other tables; we assume SQLite
- # no foreign keys just keeps the names unchanged, so
- # when we rename back, they match again.
- const_copy = _copy(
- const, schema=schema, target_table=self.table
- )
- else:
- # "target_table" for ForeignKeyConstraint.copy() is
- # only used if the FK is detected as being
- # self-referential, which we are handling above.
- const_copy = _copy(const, schema=schema)
- else:
- const_copy = _copy(
- const, schema=schema, target_table=new_table
- )
- if isinstance(const, ForeignKeyConstraint):
- self._setup_referent(m, const)
- new_table.append_constraint(const_copy)
-
- def _gather_indexes_from_both_tables(self) -> List[Index]:
- assert self.new_table is not None
- idx: List[Index] = []
-
- for idx_existing in self.indexes.values():
- # this is a lift-and-move from Table.to_metadata
-
- if idx_existing._column_flag:
- continue
-
- idx_copy = Index(
- idx_existing.name,
- unique=idx_existing.unique,
- *[
- _copy_expression(expr, self.new_table)
- for expr in _idx_table_bound_expressions(idx_existing)
- ],
- _table=self.new_table,
- **idx_existing.kwargs,
- )
- idx.append(idx_copy)
-
- for index in self.new_indexes.values():
- idx.append(
- Index(
- index.name,
- unique=index.unique,
- *[self.new_table.c[col] for col in index.columns.keys()],
- **index.kwargs,
- )
- )
- return idx
-
- def _setup_referent(
- self, metadata: MetaData, constraint: ForeignKeyConstraint
- ) -> None:
- spec = constraint.elements[0]._get_colspec()
- parts = spec.split(".")
- tname = parts[-2]
- if len(parts) == 3:
- referent_schema = parts[0]
- else:
- referent_schema = None
-
- if tname != self.temp_table_name:
- key = sql_schema._get_table_key(tname, referent_schema)
-
- def colspec(elem: Any):
- return elem._get_colspec()
-
- if key in metadata.tables:
- t = metadata.tables[key]
- for elem in constraint.elements:
- colname = colspec(elem).split(".")[-1]
- if colname not in t.c:
- t.append_column(Column(colname, sqltypes.NULLTYPE))
- else:
- Table(
- tname,
- metadata,
- *[
- Column(n, sqltypes.NULLTYPE)
- for n in [
- colspec(elem).split(".")[-1]
- for elem in constraint.elements
- ]
- ],
- schema=referent_schema,
- )
-
- def _create(self, op_impl: DefaultImpl) -> None:
- self._transfer_elements_to_new_table()
-
- op_impl.prep_table_for_batch(self, self.table)
- assert self.new_table is not None
- op_impl.create_table(self.new_table)
-
- try:
- op_impl._exec(
- self.new_table.insert()
- .inline()
- .from_select(
- list(
- k
- for k, transfer in self.column_transfers.items()
- if "expr" in transfer
- ),
- select(
- *[
- transfer["expr"]
- for transfer in self.column_transfers.values()
- if "expr" in transfer
- ]
- ),
- )
- )
- op_impl.drop_table(self.table)
- except:
- op_impl.drop_table(self.new_table)
- raise
- else:
- op_impl.rename_table(
- self.temp_table_name, self.table.name, schema=self.table.schema
- )
- self.new_table.name = self.table.name
- try:
- for idx in self._gather_indexes_from_both_tables():
- op_impl.create_index(idx)
- finally:
- self.new_table.name = self.temp_table_name
-
- def alter_column(
- self,
- table_name: str,
- column_name: str,
- nullable: Optional[bool] = None,
- server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- name: Optional[str] = None,
- type_: Optional[TypeEngine] = None,
- autoincrement: Optional[Union[bool, Literal["auto"]]] = None,
- comment: Union[str, Literal[False]] = False,
- **kw,
- ) -> None:
- existing = self.columns[column_name]
- existing_transfer: Dict[str, Any] = self.column_transfers[column_name]
- if name is not None and name != column_name:
- # note that we don't change '.key' - we keep referring
- # to the renamed column by its old key in _create(). neat!
- existing.name = name
- existing_transfer["name"] = name
-
- existing_type = kw.get("existing_type", None)
- if existing_type:
- resolved_existing_type = _resolve_for_variant(
- kw["existing_type"], self.impl.dialect
- )
-
- # pop named constraints for Boolean/Enum for rename
- if (
- isinstance(resolved_existing_type, SchemaEventTarget)
- and resolved_existing_type.name # type:ignore[attr-defined] # noqa E501
- ):
- self.named_constraints.pop(
- resolved_existing_type.name, # type:ignore[attr-defined] # noqa E501
- None,
- )
-
- if type_ is not None:
- type_ = sqltypes.to_instance(type_)
- # old type is being discarded so turn off eventing
- # rules. Alternatively we can
- # erase the events set up by this type, but this is simpler.
- # we also ignore the drop_constraint that will come here from
- # Operations.implementation_for(alter_column)
-
- if isinstance(existing.type, SchemaEventTarget):
- existing.type._create_events = ( # type:ignore[attr-defined]
- existing.type.create_constraint # type:ignore[attr-defined] # noqa
- ) = False
-
- self.impl.cast_for_batch_migrate(
- existing, existing_transfer, type_
- )
-
- existing.type = type_
-
- # we *dont* however set events for the new type, because
- # alter_column is invoked from
- # Operations.implementation_for(alter_column) which already
- # will emit an add_constraint()
-
- if nullable is not None:
- existing.nullable = nullable
- if server_default is not False:
- if server_default is None:
- existing.server_default = None
- else:
- sql_schema.DefaultClause(
- server_default # type: ignore[arg-type]
- )._set_parent(existing)
- if autoincrement is not None:
- existing.autoincrement = bool(autoincrement)
-
- if comment is not False:
- existing.comment = comment
-
- def _setup_dependencies_for_add_column(
- self,
- colname: str,
- insert_before: Optional[str],
- insert_after: Optional[str],
- ) -> None:
- index_cols = self.existing_ordering
- col_indexes = {name: i for i, name in enumerate(index_cols)}
-
- if not self.partial_reordering:
- if insert_after:
- if not insert_before:
- if insert_after in col_indexes:
- # insert after an existing column
- idx = col_indexes[insert_after] + 1
- if idx < len(index_cols):
- insert_before = index_cols[idx]
- else:
- # insert after a column that is also new
- insert_before = dict(self.add_col_ordering)[
- insert_after
- ]
- if insert_before:
- if not insert_after:
- if insert_before in col_indexes:
- # insert before an existing column
- idx = col_indexes[insert_before] - 1
- if idx >= 0:
- insert_after = index_cols[idx]
- else:
- # insert before a column that is also new
- insert_after = {
- b: a for a, b in self.add_col_ordering
- }[insert_before]
-
- if insert_before:
- self.add_col_ordering += ((colname, insert_before),)
- if insert_after:
- self.add_col_ordering += ((insert_after, colname),)
-
- if (
- not self.partial_reordering
- and not insert_before
- and not insert_after
- and col_indexes
- ):
- self.add_col_ordering += ((index_cols[-1], colname),)
-
- def add_column(
- self,
- table_name: str,
- column: Column[Any],
- insert_before: Optional[str] = None,
- insert_after: Optional[str] = None,
- **kw,
- ) -> None:
- self._setup_dependencies_for_add_column(
- column.name, insert_before, insert_after
- )
- # we copy the column because operations.add_column()
- # gives us a Column that is part of a Table already.
- self.columns[column.name] = _copy(column, schema=self.table.schema)
- self.column_transfers[column.name] = {}
-
- def drop_column(
- self,
- table_name: str,
- column: Union[ColumnClause[Any], Column[Any]],
- **kw,
- ) -> None:
- if column.name in self.table.primary_key.columns:
- _remove_column_from_collection(
- self.table.primary_key.columns, column
- )
- del self.columns[column.name]
- del self.column_transfers[column.name]
- self.existing_ordering.remove(column.name)
-
- # pop named constraints for Boolean/Enum for rename
- if (
- "existing_type" in kw
- and isinstance(kw["existing_type"], SchemaEventTarget)
- and kw["existing_type"].name # type:ignore[attr-defined]
- ):
- self.named_constraints.pop(
- kw["existing_type"].name, None # type:ignore[attr-defined]
- )
-
- def create_column_comment(self, column):
- """the batch table creation function will issue create_column_comment
- on the real "impl" as part of the create table process.
-
- That is, the Column object will have the comment on it already,
- so when it is received by add_column() it will be a normal part of
- the CREATE TABLE and doesn't need an extra step here.
-
- """
-
- def create_table_comment(self, table):
- """the batch table creation function will issue create_table_comment
- on the real "impl" as part of the create table process.
-
- """
-
- def drop_table_comment(self, table):
- """the batch table creation function will issue drop_table_comment
- on the real "impl" as part of the create table process.
-
- """
-
- def add_constraint(self, const: Constraint) -> None:
- if not constraint_name_defined(const.name):
- raise ValueError("Constraint must have a name")
- if isinstance(const, sql_schema.PrimaryKeyConstraint):
- if self.table.primary_key in self.unnamed_constraints:
- self.unnamed_constraints.remove(self.table.primary_key)
-
- if constraint_name_string(const.name):
- self.named_constraints[const.name] = const
- else:
- self.unnamed_constraints.append(const)
-
- def drop_constraint(self, const: Constraint) -> None:
- if not const.name:
- raise ValueError("Constraint must have a name")
- try:
- if const.name in self.col_named_constraints:
- col, const = self.col_named_constraints.pop(const.name)
-
- for col_const in list(self.columns[col.name].constraints):
- if col_const.name == const.name:
- self.columns[col.name].constraints.remove(col_const)
- elif constraint_name_string(const.name):
- const = self.named_constraints.pop(const.name)
- elif const in self.unnamed_constraints:
- self.unnamed_constraints.remove(const)
-
- except KeyError:
- if _is_type_bound(const):
- # type-bound constraints are only included in the new
- # table via their type object in any case, so ignore the
- # drop_constraint() that comes here via the
- # Operations.implementation_for(alter_column)
- return
- raise ValueError("No such constraint: '%s'" % const.name)
- else:
- if isinstance(const, PrimaryKeyConstraint):
- for col in const.columns:
- self.columns[col.name].primary_key = False
-
- def create_index(self, idx: Index) -> None:
- self.new_indexes[idx.name] = idx # type: ignore[index]
-
- def drop_index(self, idx: Index) -> None:
- try:
- del self.indexes[idx.name] # type: ignore[arg-type]
- except KeyError:
- raise ValueError("No such index: '%s'" % idx.name)
-
- def rename_table(self, *arg, **kw):
- raise NotImplementedError("TODO")
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/ops.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/ops.py
deleted file mode 100644
index 575af2a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/ops.py
+++ /dev/null
@@ -1,2868 +0,0 @@
-from __future__ import annotations
-
-from abc import abstractmethod
-import os
-import pathlib
-import re
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import FrozenSet
-from typing import Iterator
-from typing import List
-from typing import MutableMapping
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy.types import NULLTYPE
-
-from . import schemaobj
-from .base import BatchOperations
-from .base import Operations
-from .. import util
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from typing import Literal
-
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.elements import conv
- from sqlalchemy.sql.elements import quoted_name
- from sqlalchemy.sql.elements import TextClause
- from sqlalchemy.sql.schema import CheckConstraint
- from sqlalchemy.sql.schema import Column
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.schema import ForeignKeyConstraint
- from sqlalchemy.sql.schema import Index
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import PrimaryKeyConstraint
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.schema import UniqueConstraint
- from sqlalchemy.sql.selectable import TableClause
- from sqlalchemy.sql.type_api import TypeEngine
-
- from ..autogenerate.rewriter import Rewriter
- from ..ddl.base import _ServerDefaultType
- from ..runtime.migration import MigrationContext
- from ..script.revision import _RevIdType
-
-_T = TypeVar("_T", bound=Any)
-_AC = TypeVar("_AC", bound="AddConstraintOp")
-
-
-class MigrateOperation:
- """base class for migration command and organization objects.
-
- This system is part of the operation extensibility API.
-
- .. seealso::
-
- :ref:`operation_objects`
-
- :ref:`operation_plugins`
-
- :ref:`customizing_revision`
-
- """
-
- @util.memoized_property
- def info(self) -> Dict[Any, Any]:
- """A dictionary that may be used to store arbitrary information
- along with this :class:`.MigrateOperation` object.
-
- """
- return {}
-
- _mutations: FrozenSet[Rewriter] = frozenset()
-
- def reverse(self) -> MigrateOperation:
- raise NotImplementedError
-
- def to_diff_tuple(self) -> Tuple[Any, ...]:
- raise NotImplementedError
-
-
-class AddConstraintOp(MigrateOperation):
- """Represent an add constraint operation."""
-
- add_constraint_ops = util.Dispatcher()
-
- @property
- def constraint_type(self) -> str:
- raise NotImplementedError()
-
- @classmethod
- def register_add_constraint(
- cls, type_: str
- ) -> Callable[[Type[_AC]], Type[_AC]]:
- def go(klass: Type[_AC]) -> Type[_AC]:
- cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint)
- return klass
-
- return go
-
- @classmethod
- def from_constraint(cls, constraint: Constraint) -> AddConstraintOp:
- return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501
- constraint
- )
-
- @abstractmethod
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Constraint:
- pass
-
- def reverse(self) -> DropConstraintOp:
- return DropConstraintOp.from_constraint(self.to_constraint())
-
- def to_diff_tuple(self) -> Tuple[str, Constraint]:
- return ("add_constraint", self.to_constraint())
-
-
-@Operations.register_operation("drop_constraint")
-@BatchOperations.register_operation("drop_constraint", "batch_drop_constraint")
-class DropConstraintOp(MigrateOperation):
- """Represent a drop constraint operation."""
-
- def __init__(
- self,
- constraint_name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- type_: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- _reverse: Optional[AddConstraintOp] = None,
- ) -> None:
- self.constraint_name = constraint_name
- self.table_name = table_name
- self.constraint_type = type_
- self.schema = schema
- self.if_exists = if_exists
- self._reverse = _reverse
-
- def reverse(self) -> AddConstraintOp:
- return AddConstraintOp.from_constraint(self.to_constraint())
-
- def to_diff_tuple(
- self,
- ) -> Tuple[str, SchemaItem]:
- if self.constraint_type == "foreignkey":
- return ("remove_fk", self.to_constraint())
- else:
- return ("remove_constraint", self.to_constraint())
-
- @classmethod
- def from_constraint(cls, constraint: Constraint) -> DropConstraintOp:
- types = {
- "unique_constraint": "unique",
- "foreign_key_constraint": "foreignkey",
- "primary_key_constraint": "primary",
- "check_constraint": "check",
- "column_check_constraint": "check",
- "table_or_column_check_constraint": "check",
- }
-
- constraint_table = sqla_compat._table_for_constraint(constraint)
- return cls(
- sqla_compat.constraint_name_or_none(constraint.name),
- constraint_table.name,
- schema=constraint_table.schema,
- type_=types.get(constraint.__visit_name__),
- _reverse=AddConstraintOp.from_constraint(constraint),
- )
-
- def to_constraint(self) -> Constraint:
- if self._reverse is not None:
- constraint = self._reverse.to_constraint()
- constraint.name = self.constraint_name
- constraint_table = sqla_compat._table_for_constraint(constraint)
- constraint_table.name = self.table_name
- constraint_table.schema = self.schema
-
- return constraint
- else:
- raise ValueError(
- "constraint cannot be produced; "
- "original constraint is not present"
- )
-
- @classmethod
- def drop_constraint(
- cls,
- operations: Operations,
- constraint_name: str,
- table_name: str,
- type_: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- ) -> None:
- r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
-
- :param constraint_name: name of the constraint.
- :param table_name: table name.
- :param type\_: optional, required on MySQL. can be
- 'foreignkey', 'primary', 'unique', or 'check'.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the constraint
-
- .. versionadded:: 1.16.0
-
- """
-
- op = cls(
- constraint_name,
- table_name,
- type_=type_,
- schema=schema,
- if_exists=if_exists,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_drop_constraint(
- cls,
- operations: BatchOperations,
- constraint_name: str,
- type_: Optional[str] = None,
- ) -> None:
- """Issue a "drop constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``table_name`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.drop_constraint`
-
- """
- op = cls(
- constraint_name,
- operations.impl.table_name,
- type_=type_,
- schema=operations.impl.schema,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_primary_key")
-@BatchOperations.register_operation(
- "create_primary_key", "batch_create_primary_key"
-)
-@AddConstraintOp.register_add_constraint("primary_key_constraint")
-class CreatePrimaryKeyOp(AddConstraintOp):
- """Represent a create primary key operation."""
-
- constraint_type = "primarykey"
-
- def __init__(
- self,
- constraint_name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- columns: Sequence[str],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- self.constraint_name = constraint_name
- self.table_name = table_name
- self.columns = columns
- self.schema = schema
- self.kw = kw
-
- @classmethod
- def from_constraint(cls, constraint: Constraint) -> CreatePrimaryKeyOp:
- constraint_table = sqla_compat._table_for_constraint(constraint)
- pk_constraint = cast("PrimaryKeyConstraint", constraint)
- return cls(
- sqla_compat.constraint_name_or_none(pk_constraint.name),
- constraint_table.name,
- pk_constraint.columns.keys(),
- schema=constraint_table.schema,
- **pk_constraint.dialect_kwargs,
- )
-
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> PrimaryKeyConstraint:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- return schema_obj.primary_key_constraint(
- self.constraint_name,
- self.table_name,
- self.columns,
- schema=self.schema,
- **self.kw,
- )
-
- @classmethod
- def create_primary_key(
- cls,
- operations: Operations,
- constraint_name: Optional[str],
- table_name: str,
- columns: List[str],
- *,
- schema: Optional[str] = None,
- ) -> None:
- """Issue a "create primary key" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_primary_key("pk_my_table", "my_table", ["id", "version"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.PrimaryKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the primary key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the target table.
- :param columns: a list of string column names to be applied to the
- primary key constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
- op = cls(constraint_name, table_name, columns, schema=schema)
- return operations.invoke(op)
-
- @classmethod
- def batch_create_primary_key(
- cls,
- operations: BatchOperations,
- constraint_name: Optional[str],
- columns: List[str],
- ) -> None:
- """Issue a "create primary key" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``table_name`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_primary_key`
-
- """
- op = cls(
- constraint_name,
- operations.impl.table_name,
- columns,
- schema=operations.impl.schema,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_unique_constraint")
-@BatchOperations.register_operation(
- "create_unique_constraint", "batch_create_unique_constraint"
-)
-@AddConstraintOp.register_add_constraint("unique_constraint")
-class CreateUniqueConstraintOp(AddConstraintOp):
- """Represent a create unique constraint operation."""
-
- constraint_type = "unique"
-
- def __init__(
- self,
- constraint_name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- columns: Sequence[str],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- self.constraint_name = constraint_name
- self.table_name = table_name
- self.columns = columns
- self.schema = schema
- self.kw = kw
-
- @classmethod
- def from_constraint(
- cls, constraint: Constraint
- ) -> CreateUniqueConstraintOp:
- constraint_table = sqla_compat._table_for_constraint(constraint)
-
- uq_constraint = cast("UniqueConstraint", constraint)
-
- kw: Dict[str, Any] = {}
- if uq_constraint.deferrable:
- kw["deferrable"] = uq_constraint.deferrable
- if uq_constraint.initially:
- kw["initially"] = uq_constraint.initially
- kw.update(uq_constraint.dialect_kwargs)
- return cls(
- sqla_compat.constraint_name_or_none(uq_constraint.name),
- constraint_table.name,
- [c.name for c in uq_constraint.columns],
- schema=constraint_table.schema,
- **kw,
- )
-
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> UniqueConstraint:
- schema_obj = schemaobj.SchemaObjects(migration_context)
- return schema_obj.unique_constraint(
- self.constraint_name,
- self.table_name,
- self.columns,
- schema=self.schema,
- **self.kw,
- )
-
- @classmethod
- def create_unique_constraint(
- cls,
- operations: Operations,
- constraint_name: Optional[str],
- table_name: str,
- columns: Sequence[str],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> Any:
- """Issue a "create unique constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- op.create_unique_constraint("uq_user_name", "user", ["name"])
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.UniqueConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param name: Name of the unique constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param columns: a list of string column names in the
- source table.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
-
- op = cls(constraint_name, table_name, columns, schema=schema, **kw)
- return operations.invoke(op)
-
- @classmethod
- def batch_create_unique_constraint(
- cls,
- operations: BatchOperations,
- constraint_name: str,
- columns: Sequence[str],
- **kw: Any,
- ) -> Any:
- """Issue a "create unique constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_unique_constraint`
-
- """
- kw["schema"] = operations.impl.schema
- op = cls(constraint_name, operations.impl.table_name, columns, **kw)
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_foreign_key")
-@BatchOperations.register_operation(
- "create_foreign_key", "batch_create_foreign_key"
-)
-@AddConstraintOp.register_add_constraint("foreign_key_constraint")
-class CreateForeignKeyOp(AddConstraintOp):
- """Represent a create foreign key constraint operation."""
-
- constraint_type = "foreignkey"
-
- def __init__(
- self,
- constraint_name: Optional[sqla_compat._ConstraintNameDefined],
- source_table: str,
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- **kw: Any,
- ) -> None:
- self.constraint_name = constraint_name
- self.source_table = source_table
- self.referent_table = referent_table
- self.local_cols = local_cols
- self.remote_cols = remote_cols
- self.kw = kw
-
- def to_diff_tuple(self) -> Tuple[str, ForeignKeyConstraint]:
- return ("add_fk", self.to_constraint())
-
- @classmethod
- def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp:
- fk_constraint = cast("ForeignKeyConstraint", constraint)
- kw: Dict[str, Any] = {}
- if fk_constraint.onupdate:
- kw["onupdate"] = fk_constraint.onupdate
- if fk_constraint.ondelete:
- kw["ondelete"] = fk_constraint.ondelete
- if fk_constraint.initially:
- kw["initially"] = fk_constraint.initially
- if fk_constraint.deferrable:
- kw["deferrable"] = fk_constraint.deferrable
- if fk_constraint.use_alter:
- kw["use_alter"] = fk_constraint.use_alter
- if fk_constraint.match:
- kw["match"] = fk_constraint.match
-
- (
- source_schema,
- source_table,
- source_columns,
- target_schema,
- target_table,
- target_columns,
- onupdate,
- ondelete,
- deferrable,
- initially,
- ) = sqla_compat._fk_spec(fk_constraint)
-
- kw["source_schema"] = source_schema
- kw["referent_schema"] = target_schema
- kw.update(fk_constraint.dialect_kwargs)
- return cls(
- sqla_compat.constraint_name_or_none(fk_constraint.name),
- source_table,
- target_table,
- source_columns,
- target_columns,
- **kw,
- )
-
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> ForeignKeyConstraint:
- schema_obj = schemaobj.SchemaObjects(migration_context)
- return schema_obj.foreign_key_constraint(
- self.constraint_name,
- self.source_table,
- self.referent_table,
- self.local_cols,
- self.remote_cols,
- **self.kw,
- )
-
- @classmethod
- def create_foreign_key(
- cls,
- operations: Operations,
- constraint_name: Optional[str],
- source_table: str,
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- *,
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- source_schema: Optional[str] = None,
- referent_schema: Optional[str] = None,
- **dialect_kw: Any,
- ) -> None:
- """Issue a "create foreign key" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_foreign_key(
- "fk_user_address",
- "address",
- "user",
- ["user_id"],
- ["id"],
- )
-
- This internally generates a :class:`~sqlalchemy.schema.Table` object
- containing the necessary columns, then generates a new
- :class:`~sqlalchemy.schema.ForeignKeyConstraint`
- object which it then associates with the
- :class:`~sqlalchemy.schema.Table`.
- Any event listeners associated with this action will be fired
- off normally. The :class:`~sqlalchemy.schema.AddConstraint`
- construct is ultimately used to generate the ALTER statement.
-
- :param constraint_name: Name of the foreign key constraint. The name
- is necessary so that an ALTER statement can be emitted. For setups
- that use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param source_table: String name of the source table.
- :param referent_table: String name of the destination table.
- :param local_cols: a list of string column names in the
- source table.
- :param remote_cols: a list of string column names in the
- remote table.
- :param onupdate: Optional string. If set, emit ON UPDATE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param ondelete: Optional string. If set, emit ON DELETE when
- issuing DDL for this constraint. Typical values include CASCADE,
- DELETE and RESTRICT.
- :param deferrable: optional bool. If set, emit DEFERRABLE or NOT
- DEFERRABLE when issuing DDL for this constraint.
- :param source_schema: Optional schema name of the source table.
- :param referent_schema: Optional schema name of the destination table.
-
- """
-
- op = cls(
- constraint_name,
- source_table,
- referent_table,
- local_cols,
- remote_cols,
- onupdate=onupdate,
- ondelete=ondelete,
- deferrable=deferrable,
- source_schema=source_schema,
- referent_schema=referent_schema,
- initially=initially,
- match=match,
- **dialect_kw,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_create_foreign_key(
- cls,
- operations: BatchOperations,
- constraint_name: Optional[str],
- referent_table: str,
- local_cols: List[str],
- remote_cols: List[str],
- *,
- referent_schema: Optional[str] = None,
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- **dialect_kw: Any,
- ) -> None:
- """Issue a "create foreign key" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``source_schema``
- arguments from the call.
-
- e.g.::
-
- with batch_alter_table("address") as batch_op:
- batch_op.create_foreign_key(
- "fk_user_address",
- "user",
- ["user_id"],
- ["id"],
- )
-
- .. seealso::
-
- :meth:`.Operations.create_foreign_key`
-
- """
- op = cls(
- constraint_name,
- operations.impl.table_name,
- referent_table,
- local_cols,
- remote_cols,
- onupdate=onupdate,
- ondelete=ondelete,
- deferrable=deferrable,
- source_schema=operations.impl.schema,
- referent_schema=referent_schema,
- initially=initially,
- match=match,
- **dialect_kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_check_constraint")
-@BatchOperations.register_operation(
- "create_check_constraint", "batch_create_check_constraint"
-)
-@AddConstraintOp.register_add_constraint("check_constraint")
-@AddConstraintOp.register_add_constraint("table_or_column_check_constraint")
-@AddConstraintOp.register_add_constraint("column_check_constraint")
-class CreateCheckConstraintOp(AddConstraintOp):
- """Represent a create check constraint operation."""
-
- constraint_type = "check"
-
- def __init__(
- self,
- constraint_name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- condition: Union[str, TextClause, ColumnElement[Any]],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- self.constraint_name = constraint_name
- self.table_name = table_name
- self.condition = condition
- self.schema = schema
- self.kw = kw
-
- @classmethod
- def from_constraint(
- cls, constraint: Constraint
- ) -> CreateCheckConstraintOp:
- constraint_table = sqla_compat._table_for_constraint(constraint)
-
- ck_constraint = cast("CheckConstraint", constraint)
- return cls(
- sqla_compat.constraint_name_or_none(ck_constraint.name),
- constraint_table.name,
- cast("ColumnElement[Any]", ck_constraint.sqltext),
- schema=constraint_table.schema,
- **ck_constraint.dialect_kwargs,
- )
-
- def to_constraint(
- self, migration_context: Optional[MigrationContext] = None
- ) -> CheckConstraint:
- schema_obj = schemaobj.SchemaObjects(migration_context)
- return schema_obj.check_constraint(
- self.constraint_name,
- self.table_name,
- self.condition,
- schema=self.schema,
- **self.kw,
- )
-
- @classmethod
- def create_check_constraint(
- cls,
- operations: Operations,
- constraint_name: Optional[str],
- table_name: str,
- condition: Union[str, ColumnElement[bool], TextClause],
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue a "create check constraint" instruction using the
- current migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy.sql import column, func
-
- op.create_check_constraint(
- "ck_user_name_len",
- "user",
- func.len(column("name")) > 5,
- )
-
- CHECK constraints are usually against a SQL expression, so ad-hoc
- table metadata is usually needed. The function will convert the given
- arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound
- to an anonymous table in order to emit the CREATE statement.
-
- :param name: Name of the check constraint. The name is necessary
- so that an ALTER statement can be emitted. For setups that
- use an automated naming scheme such as that described at
- :ref:`sqla:constraint_naming_conventions`,
- ``name`` here can be ``None``, as the event listener will
- apply the name to the constraint object when it is associated
- with the table.
- :param table_name: String name of the source table.
- :param condition: SQL expression that's the condition of the
- constraint. Can be a string or SQLAlchemy expression language
- structure.
- :param deferrable: optional bool. If set, emit DEFERRABLE or
- NOT DEFERRABLE when issuing DDL for this constraint.
- :param initially: optional string. If set, emit INITIALLY
- when issuing DDL for this constraint.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
- op = cls(constraint_name, table_name, condition, schema=schema, **kw)
- return operations.invoke(op)
-
- @classmethod
- def batch_create_check_constraint(
- cls,
- operations: BatchOperations,
- constraint_name: str,
- condition: Union[str, ColumnElement[bool], TextClause],
- **kw: Any,
- ) -> None:
- """Issue a "create check constraint" instruction using the
- current batch migration context.
-
- The batch form of this call omits the ``source`` and ``schema``
- arguments from the call.
-
- .. seealso::
-
- :meth:`.Operations.create_check_constraint`
-
- """
- op = cls(
- constraint_name,
- operations.impl.table_name,
- condition,
- schema=operations.impl.schema,
- **kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_index")
-@BatchOperations.register_operation("create_index", "batch_create_index")
-class CreateIndexOp(MigrateOperation):
- """Represent a create index operation."""
-
- def __init__(
- self,
- index_name: Optional[str],
- table_name: str,
- columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
- *,
- schema: Optional[str] = None,
- unique: bool = False,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- self.index_name = index_name
- self.table_name = table_name
- self.columns = columns
- self.schema = schema
- self.unique = unique
- self.if_not_exists = if_not_exists
- self.kw = kw
-
- def reverse(self) -> DropIndexOp:
- return DropIndexOp.from_index(self.to_index())
-
- def to_diff_tuple(self) -> Tuple[str, Index]:
- return ("add_index", self.to_index())
-
- @classmethod
- def from_index(cls, index: Index) -> CreateIndexOp:
- assert index.table is not None
- return cls(
- index.name,
- index.table.name,
- index.expressions,
- schema=index.table.schema,
- unique=index.unique,
- **index.kwargs,
- )
-
- def to_index(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Index:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- idx = schema_obj.index(
- self.index_name,
- self.table_name,
- self.columns,
- schema=self.schema,
- unique=self.unique,
- **self.kw,
- )
- return idx
-
- @classmethod
- def create_index(
- cls,
- operations: Operations,
- index_name: Optional[str],
- table_name: str,
- columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
- *,
- schema: Optional[str] = None,
- unique: bool = False,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "create index" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
-
- op.create_index("ik_test", "t1", ["foo", "bar"])
-
- Functional indexes can be produced by using the
- :func:`sqlalchemy.sql.expression.text` construct::
-
- from alembic import op
- from sqlalchemy import text
-
- op.create_index("ik_test", "t1", [text("lower(foo)")])
-
- :param index_name: name of the index.
- :param table_name: name of the owning table.
- :param columns: a list consisting of string column names and/or
- :func:`~sqlalchemy.sql.expression.text` constructs.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param unique: If True, create a unique index.
-
- :param quote: Force quoting of this column's name on or off,
- corresponding to ``True`` or ``False``. When left at its default
- of ``None``, the column identifier will be quoted according to
- whether the name is case sensitive (identifiers with at least one
- upper case character are treated as case sensitive), or if it's a
- reserved word. This flag is only needed to force quoting of a
- reserved word which is not known by the SQLAlchemy dialect.
-
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """
- op = cls(
- index_name,
- table_name,
- columns,
- schema=schema,
- unique=unique,
- if_not_exists=if_not_exists,
- **kw,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_create_index(
- cls,
- operations: BatchOperations,
- index_name: str,
- columns: List[str],
- **kw: Any,
- ) -> None:
- """Issue a "create index" instruction using the
- current batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.create_index`
-
- """
-
- op = cls(
- index_name,
- operations.impl.table_name,
- columns,
- schema=operations.impl.schema,
- **kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("drop_index")
-@BatchOperations.register_operation("drop_index", "batch_drop_index")
-class DropIndexOp(MigrateOperation):
- """Represent a drop index operation."""
-
- def __init__(
- self,
- index_name: Union[quoted_name, str, conv],
- table_name: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- _reverse: Optional[CreateIndexOp] = None,
- **kw: Any,
- ) -> None:
- self.index_name = index_name
- self.table_name = table_name
- self.schema = schema
- self.if_exists = if_exists
- self._reverse = _reverse
- self.kw = kw
-
- def to_diff_tuple(self) -> Tuple[str, Index]:
- return ("remove_index", self.to_index())
-
- def reverse(self) -> CreateIndexOp:
- return CreateIndexOp.from_index(self.to_index())
-
- @classmethod
- def from_index(cls, index: Index) -> DropIndexOp:
- assert index.table is not None
- return cls(
- index.name, # type: ignore[arg-type]
- table_name=index.table.name,
- schema=index.table.schema,
- _reverse=CreateIndexOp.from_index(index),
- unique=index.unique,
- **index.kwargs,
- )
-
- def to_index(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Index:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- # need a dummy column name here since SQLAlchemy
- # 0.7.6 and further raises on Index with no columns
- return schema_obj.index(
- self.index_name,
- self.table_name,
- self._reverse.columns if self._reverse else ["x"],
- schema=self.schema,
- **self.kw,
- )
-
- @classmethod
- def drop_index(
- cls,
- operations: Operations,
- index_name: str,
- table_name: Optional[str] = None,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "drop index" instruction using the current
- migration context.
-
- e.g.::
-
- drop_index("accounts")
-
- :param index_name: name of the index.
- :param table_name: name of the owning table. Some
- backends such as Microsoft SQL Server require this.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the index.
-
- .. versionadded:: 1.12.0
-
- :param \**kw: Additional keyword arguments not mentioned above are
- dialect specific, and passed in the form
- ``_``.
- See the documentation regarding an individual dialect at
- :ref:`dialect_toplevel` for detail on documented arguments.
-
- """
- op = cls(
- index_name,
- table_name=table_name,
- schema=schema,
- if_exists=if_exists,
- **kw,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_drop_index(
- cls, operations: BatchOperations, index_name: str, **kw: Any
- ) -> None:
- """Issue a "drop index" instruction using the
- current batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.drop_index`
-
- """
-
- op = cls(
- index_name,
- table_name=operations.impl.table_name,
- schema=operations.impl.schema,
- **kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_table")
-class CreateTableOp(MigrateOperation):
- """Represent a create table operation."""
-
- def __init__(
- self,
- table_name: str,
- columns: Sequence[SchemaItem],
- *,
- schema: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- _namespace_metadata: Optional[MetaData] = None,
- _constraints_included: bool = False,
- **kw: Any,
- ) -> None:
- self.table_name = table_name
- self.columns = columns
- self.schema = schema
- self.if_not_exists = if_not_exists
- self.info = kw.pop("info", {})
- self.comment = kw.pop("comment", None)
- self.prefixes = kw.pop("prefixes", None)
- self.kw = kw
- self._namespace_metadata = _namespace_metadata
- self._constraints_included = _constraints_included
-
- def reverse(self) -> DropTableOp:
- return DropTableOp.from_table(
- self.to_table(), _namespace_metadata=self._namespace_metadata
- )
-
- def to_diff_tuple(self) -> Tuple[str, Table]:
- return ("add_table", self.to_table())
-
- @classmethod
- def from_table(
- cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None
- ) -> CreateTableOp:
- if _namespace_metadata is None:
- _namespace_metadata = table.metadata
-
- return cls(
- table.name,
- list(table.c) + list(table.constraints),
- schema=table.schema,
- _namespace_metadata=_namespace_metadata,
- # given a Table() object, this Table will contain full Index()
- # and UniqueConstraint objects already constructed in response to
- # each unique=True / index=True flag on a Column. Carry this
- # state along so that when we re-convert back into a Table, we
- # skip unique=True/index=True so that these constraints are
- # not doubled up. see #844 #848
- _constraints_included=True,
- comment=table.comment,
- info=dict(table.info),
- prefixes=list(table._prefixes),
- **table.kwargs,
- )
-
- def to_table(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Table:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- return schema_obj.table(
- self.table_name,
- *self.columns,
- schema=self.schema,
- prefixes=list(self.prefixes) if self.prefixes else [],
- comment=self.comment,
- info=self.info.copy() if self.info else {},
- _constraints_included=self._constraints_included,
- **self.kw,
- )
-
- @classmethod
- def create_table(
- cls,
- operations: Operations,
- table_name: str,
- *columns: SchemaItem,
- if_not_exists: Optional[bool] = None,
- **kw: Any,
- ) -> Table:
- r"""Issue a "create table" instruction using the current migration
- context.
-
- This directive receives an argument list similar to that of the
- traditional :class:`sqlalchemy.schema.Table` construct, but without the
- metadata::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- Note that :meth:`.create_table` accepts
- :class:`~sqlalchemy.schema.Column`
- constructs directly from the SQLAlchemy library. In particular,
- default values to be created on the database side are
- specified using the ``server_default`` parameter, and not
- ``default`` which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the "timestamp" column
- op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- The function also returns a newly created
- :class:`~sqlalchemy.schema.Table` object, corresponding to the table
- specification given, which is suitable for
- immediate SQL operations, in particular
- :meth:`.Operations.bulk_insert`::
-
- from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column
- from alembic import op
-
- account_table = op.create_table(
- "account",
- Column("id", INTEGER, primary_key=True),
- Column("name", VARCHAR(50), nullable=False),
- Column("description", NVARCHAR(200)),
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- op.bulk_insert(
- account_table,
- [
- {"name": "A1", "description": "account 1"},
- {"name": "A2", "description": "account 2"},
- ],
- )
-
- :param table_name: Name of the table
- :param \*columns: collection of :class:`~sqlalchemy.schema.Column`
- objects within
- the table, as well as optional :class:`~sqlalchemy.schema.Constraint`
- objects
- and :class:`~.sqlalchemy.schema.Index` objects.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator when
- creating the new table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- :return: the :class:`~sqlalchemy.schema.Table` object corresponding
- to the parameters given.
-
- """
- op = cls(table_name, columns, if_not_exists=if_not_exists, **kw)
- return operations.invoke(op)
-
-
-@Operations.register_operation("drop_table")
-class DropTableOp(MigrateOperation):
- """Represent a drop table operation."""
-
- def __init__(
- self,
- table_name: str,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- table_kw: Optional[MutableMapping[Any, Any]] = None,
- _reverse: Optional[CreateTableOp] = None,
- ) -> None:
- self.table_name = table_name
- self.schema = schema
- self.if_exists = if_exists
- self.table_kw = table_kw or {}
- self.comment = self.table_kw.pop("comment", None)
- self.info = self.table_kw.pop("info", None)
- self.prefixes = self.table_kw.pop("prefixes", None)
- self._reverse = _reverse
-
- def to_diff_tuple(self) -> Tuple[str, Table]:
- return ("remove_table", self.to_table())
-
- def reverse(self) -> CreateTableOp:
- return CreateTableOp.from_table(self.to_table())
-
- @classmethod
- def from_table(
- cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None
- ) -> DropTableOp:
- return cls(
- table.name,
- schema=table.schema,
- table_kw={
- "comment": table.comment,
- "info": dict(table.info),
- "prefixes": list(table._prefixes),
- **table.kwargs,
- },
- _reverse=CreateTableOp.from_table(
- table, _namespace_metadata=_namespace_metadata
- ),
- )
-
- def to_table(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Table:
- if self._reverse:
- cols_and_constraints = self._reverse.columns
- else:
- cols_and_constraints = []
-
- schema_obj = schemaobj.SchemaObjects(migration_context)
- t = schema_obj.table(
- self.table_name,
- *cols_and_constraints,
- comment=self.comment,
- info=self.info.copy() if self.info else {},
- prefixes=list(self.prefixes) if self.prefixes else [],
- schema=self.schema,
- _constraints_included=(
- self._reverse._constraints_included if self._reverse else False
- ),
- **self.table_kw,
- )
- return t
-
- @classmethod
- def drop_table(
- cls,
- operations: Operations,
- table_name: str,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- r"""Issue a "drop table" instruction using the current
- migration context.
-
-
- e.g.::
-
- drop_table("accounts")
-
- :param table_name: Name of the table
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the table.
-
- .. versionadded:: 1.13.3
- :param \**kw: Other keyword arguments are passed to the underlying
- :class:`sqlalchemy.schema.Table` object created for the command.
-
- """
- op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw)
- operations.invoke(op)
-
-
-class AlterTableOp(MigrateOperation):
- """Represent an alter table operation."""
-
- def __init__(
- self,
- table_name: str,
- *,
- schema: Optional[str] = None,
- ) -> None:
- self.table_name = table_name
- self.schema = schema
-
-
-@Operations.register_operation("rename_table")
-class RenameTableOp(AlterTableOp):
- """Represent a rename table operation."""
-
- def __init__(
- self,
- old_table_name: str,
- new_table_name: str,
- *,
- schema: Optional[str] = None,
- ) -> None:
- super().__init__(old_table_name, schema=schema)
- self.new_table_name = new_table_name
-
- @classmethod
- def rename_table(
- cls,
- operations: Operations,
- old_table_name: str,
- new_table_name: str,
- *,
- schema: Optional[str] = None,
- ) -> None:
- """Emit an ALTER TABLE to rename a table.
-
- :param old_table_name: old name.
- :param new_table_name: new name.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
-
- """
- op = cls(old_table_name, new_table_name, schema=schema)
- return operations.invoke(op)
-
-
-@Operations.register_operation("create_table_comment")
-@BatchOperations.register_operation(
- "create_table_comment", "batch_create_table_comment"
-)
-class CreateTableCommentOp(AlterTableOp):
- """Represent a COMMENT ON `table` operation."""
-
- def __init__(
- self,
- table_name: str,
- comment: Optional[str],
- *,
- schema: Optional[str] = None,
- existing_comment: Optional[str] = None,
- ) -> None:
- self.table_name = table_name
- self.comment = comment
- self.existing_comment = existing_comment
- self.schema = schema
-
- @classmethod
- def create_table_comment(
- cls,
- operations: Operations,
- table_name: str,
- comment: Optional[str],
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- ) -> None:
- """Emit a COMMENT ON operation to set the comment for a table.
-
- :param table_name: string name of the target table.
- :param comment: string value of the comment being registered against
- the specified table.
- :param existing_comment: String value of a comment
- already registered on the specified table, used within autogenerate
- so that the operation is reversible, but not required for direct
- use.
-
- .. seealso::
-
- :meth:`.Operations.drop_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """
-
- op = cls(
- table_name,
- comment,
- existing_comment=existing_comment,
- schema=schema,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_create_table_comment(
- cls,
- operations: BatchOperations,
- comment: Optional[str],
- *,
- existing_comment: Optional[str] = None,
- ) -> None:
- """Emit a COMMENT ON operation to set the comment for a table
- using the current batch migration context.
-
- :param comment: string value of the comment being registered against
- the specified table.
- :param existing_comment: String value of a comment
- already registered on the specified table, used within autogenerate
- so that the operation is reversible, but not required for direct
- use.
-
- """
-
- op = cls(
- operations.impl.table_name,
- comment,
- existing_comment=existing_comment,
- schema=operations.impl.schema,
- )
- return operations.invoke(op)
-
- def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]:
- """Reverses the COMMENT ON operation against a table."""
- if self.existing_comment is None:
- return DropTableCommentOp(
- self.table_name,
- existing_comment=self.comment,
- schema=self.schema,
- )
- else:
- return CreateTableCommentOp(
- self.table_name,
- self.existing_comment,
- existing_comment=self.comment,
- schema=self.schema,
- )
-
- def to_table(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Table:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- return schema_obj.table(
- self.table_name, schema=self.schema, comment=self.comment
- )
-
- def to_diff_tuple(self) -> Tuple[Any, ...]:
- return ("add_table_comment", self.to_table(), self.existing_comment)
-
-
-@Operations.register_operation("drop_table_comment")
-@BatchOperations.register_operation(
- "drop_table_comment", "batch_drop_table_comment"
-)
-class DropTableCommentOp(AlterTableOp):
- """Represent an operation to remove the comment from a table."""
-
- def __init__(
- self,
- table_name: str,
- *,
- schema: Optional[str] = None,
- existing_comment: Optional[str] = None,
- ) -> None:
- self.table_name = table_name
- self.existing_comment = existing_comment
- self.schema = schema
-
- @classmethod
- def drop_table_comment(
- cls,
- operations: Operations,
- table_name: str,
- *,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- ) -> None:
- """Issue a "drop table comment" operation to
- remove an existing comment set on a table.
-
- :param table_name: string name of the target table.
- :param existing_comment: An optional string value of a comment already
- registered on the specified table.
-
- .. seealso::
-
- :meth:`.Operations.create_table_comment`
-
- :paramref:`.Operations.alter_column.comment`
-
- """
-
- op = cls(table_name, existing_comment=existing_comment, schema=schema)
- return operations.invoke(op)
-
- @classmethod
- def batch_drop_table_comment(
- cls,
- operations: BatchOperations,
- *,
- existing_comment: Optional[str] = None,
- ) -> None:
- """Issue a "drop table comment" operation to
- remove an existing comment set on a table using the current
- batch operations context.
-
- :param existing_comment: An optional string value of a comment already
- registered on the specified table.
-
- """
-
- op = cls(
- operations.impl.table_name,
- existing_comment=existing_comment,
- schema=operations.impl.schema,
- )
- return operations.invoke(op)
-
- def reverse(self) -> CreateTableCommentOp:
- """Reverses the COMMENT ON operation against a table."""
- return CreateTableCommentOp(
- self.table_name, self.existing_comment, schema=self.schema
- )
-
- def to_table(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Table:
- schema_obj = schemaobj.SchemaObjects(migration_context)
-
- return schema_obj.table(self.table_name, schema=self.schema)
-
- def to_diff_tuple(self) -> Tuple[Any, ...]:
- return ("remove_table_comment", self.to_table())
-
-
-@Operations.register_operation("alter_column")
-@BatchOperations.register_operation("alter_column", "batch_alter_column")
-class AlterColumnOp(AlterTableOp):
- """Represent an alter column operation."""
-
- def __init__(
- self,
- table_name: str,
- column_name: str,
- *,
- schema: Optional[str] = None,
- existing_type: Optional[Any] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- modify_nullable: Optional[bool] = None,
- modify_comment: Optional[Union[str, Literal[False]]] = False,
- modify_server_default: Any = False,
- modify_name: Optional[str] = None,
- modify_type: Optional[Any] = None,
- **kw: Any,
- ) -> None:
- super().__init__(table_name, schema=schema)
- self.column_name = column_name
- self.existing_type = existing_type
- self.existing_server_default = existing_server_default
- self.existing_nullable = existing_nullable
- self.existing_comment = existing_comment
- self.modify_nullable = modify_nullable
- self.modify_comment = modify_comment
- self.modify_server_default = modify_server_default
- self.modify_name = modify_name
- self.modify_type = modify_type
- self.kw = kw
-
- def to_diff_tuple(self) -> Any:
- col_diff = []
- schema, tname, cname = self.schema, self.table_name, self.column_name
-
- if self.modify_type is not None:
- col_diff.append(
- (
- "modify_type",
- schema,
- tname,
- cname,
- {
- "existing_nullable": self.existing_nullable,
- "existing_server_default": (
- self.existing_server_default
- ),
- "existing_comment": self.existing_comment,
- },
- self.existing_type,
- self.modify_type,
- )
- )
-
- if self.modify_nullable is not None:
- col_diff.append(
- (
- "modify_nullable",
- schema,
- tname,
- cname,
- {
- "existing_type": self.existing_type,
- "existing_server_default": (
- self.existing_server_default
- ),
- "existing_comment": self.existing_comment,
- },
- self.existing_nullable,
- self.modify_nullable,
- )
- )
-
- if self.modify_server_default is not False:
- col_diff.append(
- (
- "modify_default",
- schema,
- tname,
- cname,
- {
- "existing_nullable": self.existing_nullable,
- "existing_type": self.existing_type,
- "existing_comment": self.existing_comment,
- },
- self.existing_server_default,
- self.modify_server_default,
- )
- )
-
- if self.modify_comment is not False:
- col_diff.append(
- (
- "modify_comment",
- schema,
- tname,
- cname,
- {
- "existing_nullable": self.existing_nullable,
- "existing_type": self.existing_type,
- "existing_server_default": (
- self.existing_server_default
- ),
- },
- self.existing_comment,
- self.modify_comment,
- )
- )
-
- return col_diff
-
- def has_changes(self) -> bool:
- hc1 = (
- self.modify_nullable is not None
- or self.modify_server_default is not False
- or self.modify_type is not None
- or self.modify_comment is not False
- )
- if hc1:
- return True
- for kw in self.kw:
- if kw.startswith("modify_"):
- return True
- else:
- return False
-
- def reverse(self) -> AlterColumnOp:
- kw = self.kw.copy()
- kw["existing_type"] = self.existing_type
- kw["existing_nullable"] = self.existing_nullable
- kw["existing_server_default"] = self.existing_server_default
- kw["existing_comment"] = self.existing_comment
- if self.modify_type is not None:
- kw["modify_type"] = self.modify_type
- if self.modify_nullable is not None:
- kw["modify_nullable"] = self.modify_nullable
- if self.modify_server_default is not False:
- kw["modify_server_default"] = self.modify_server_default
- if self.modify_comment is not False:
- kw["modify_comment"] = self.modify_comment
-
- # TODO: make this a little simpler
- all_keys = {
- m.group(1)
- for m in [re.match(r"^(?:existing_|modify_)(.+)$", k) for k in kw]
- if m
- }
-
- for k in all_keys:
- if "modify_%s" % k in kw:
- swap = kw["existing_%s" % k]
- kw["existing_%s" % k] = kw["modify_%s" % k]
- kw["modify_%s" % k] = swap
-
- return self.__class__(
- self.table_name, self.column_name, schema=self.schema, **kw
- )
-
- @classmethod
- def alter_column(
- cls,
- operations: Operations,
- table_name: str,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- comment: Optional[Union[str, Literal[False]]] = False,
- server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- new_column_name: Optional[str] = None,
- type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
- existing_type: Optional[
- Union[TypeEngine[Any], Type[TypeEngine[Any]]]
- ] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- r"""Issue an "alter column" instruction using the
- current migration context.
-
- Generally, only that aspect of the column which
- is being changed, i.e. name, type, nullability,
- default, needs to be specified. Multiple changes
- can also be specified at once and the backend should
- "do the right thing", emitting each change either
- separately or together as the backend allows.
-
- MySQL has special requirements here, since MySQL
- cannot ALTER a column without a full specification.
- When producing MySQL-compatible migration files,
- it is recommended that the ``existing_type``,
- ``existing_server_default``, and ``existing_nullable``
- parameters be present, if not being altered.
-
- Type changes which are against the SQLAlchemy
- "schema" types :class:`~sqlalchemy.types.Boolean`
- and :class:`~sqlalchemy.types.Enum` may also
- add or drop constraints which accompany those
- types on backends that don't support them natively.
- The ``existing_type`` argument is
- used in this case to identify and remove a previous
- constraint that was bound to the type object.
-
- :param table_name: string name of the target table.
- :param column_name: string name of the target column,
- as it exists before the operation begins.
- :param nullable: Optional; specify ``True`` or ``False``
- to alter the column's nullability.
- :param server_default: Optional; specify a string
- SQL expression, :func:`~sqlalchemy.sql.expression.text`,
- or :class:`~sqlalchemy.schema.DefaultClause` to indicate
- an alteration to the column's default value.
- Set to ``None`` to have the default removed.
- :param comment: optional string text of a new comment to add to the
- column.
- :param new_column_name: Optional; specify a string name here to
- indicate the new name within a column rename operation.
- :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
- type object to specify a change to the column's type.
- For SQLAlchemy types that also indicate a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
- the constraint is also generated.
- :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column;
- currently understood by the MySQL dialect.
- :param existing_type: Optional; a
- :class:`~sqlalchemy.types.TypeEngine`
- type object to specify the previous type. This
- is required for all MySQL column alter operations that
- don't otherwise specify a new type, as well as for
- when nullability is being changed on a SQL Server
- column. It is also used if the type is a so-called
- SQLAlchemy "schema" type which may define a constraint (i.e.
- :class:`~sqlalchemy.types.Boolean`,
- :class:`~sqlalchemy.types.Enum`),
- so that the constraint can be dropped.
- :param existing_server_default: Optional; The existing
- default value of the column. Required on MySQL if
- an existing default is not being changed; else MySQL
- removes the default.
- :param existing_nullable: Optional; the existing nullability
- of the column. Required on MySQL if the existing nullability
- is not being changed; else MySQL sets this to NULL.
- :param existing_autoincrement: Optional; the existing autoincrement
- of the column. Used for MySQL's system of altering a column
- that specifies ``AUTO_INCREMENT``.
- :param existing_comment: string text of the existing comment on the
- column to be maintained. Required on MySQL if the existing comment
- on the column is not being changed.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param postgresql_using: String argument which will indicate a
- SQL expression to render within the Postgresql-specific USING clause
- within ALTER COLUMN. This string is taken directly as raw SQL which
- must explicitly include any necessary quoting or escaping of tokens
- within the expression.
-
- """
-
- alt = cls(
- table_name,
- column_name,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- modify_name=new_column_name,
- modify_type=type_,
- modify_server_default=server_default,
- modify_nullable=nullable,
- modify_comment=comment,
- **kw,
- )
-
- return operations.invoke(alt)
-
- @classmethod
- def batch_alter_column(
- cls,
- operations: BatchOperations,
- column_name: str,
- *,
- nullable: Optional[bool] = None,
- comment: Optional[Union[str, Literal[False]]] = False,
- server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- new_column_name: Optional[str] = None,
- type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
- existing_type: Optional[
- Union[TypeEngine[Any], Type[TypeEngine[Any]]]
- ] = None,
- existing_server_default: Union[
- _ServerDefaultType, None, Literal[False]
- ] = False,
- existing_nullable: Optional[bool] = None,
- existing_comment: Optional[str] = None,
- insert_before: Optional[str] = None,
- insert_after: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue an "alter column" instruction using the current
- batch migration context.
-
- Parameters are the same as that of :meth:`.Operations.alter_column`,
- as well as the following option(s):
-
- :param insert_before: String name of an existing column which this
- column should be placed before, when creating the new table.
-
- :param insert_after: String name of an existing column which this
- column should be placed after, when creating the new table. If
- both :paramref:`.BatchOperations.alter_column.insert_before`
- and :paramref:`.BatchOperations.alter_column.insert_after` are
- omitted, the column is inserted after the last existing column
- in the table.
-
- .. seealso::
-
- :meth:`.Operations.alter_column`
-
-
- """
- alt = cls(
- operations.impl.table_name,
- column_name,
- schema=operations.impl.schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- existing_comment=existing_comment,
- modify_name=new_column_name,
- modify_type=type_,
- modify_server_default=server_default,
- modify_nullable=nullable,
- modify_comment=comment,
- insert_before=insert_before,
- insert_after=insert_after,
- **kw,
- )
-
- return operations.invoke(alt)
-
-
-@Operations.register_operation("add_column")
-@BatchOperations.register_operation("add_column", "batch_add_column")
-class AddColumnOp(AlterTableOp):
- """Represent an add column operation."""
-
- def __init__(
- self,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- **kw: Any,
- ) -> None:
- super().__init__(table_name, schema=schema)
- self.column = column
- self.if_not_exists = if_not_exists
- self.inline_references = inline_references
- self.kw = kw
-
- def reverse(self) -> DropColumnOp:
- op = DropColumnOp.from_column_and_tablename(
- self.schema, self.table_name, self.column
- )
- op.if_exists = self.if_not_exists
- return op
-
- def to_diff_tuple(
- self,
- ) -> Tuple[str, Optional[str], str, Column[Any]]:
- return ("add_column", self.schema, self.table_name, self.column)
-
- def to_column(self) -> Column[Any]:
- return self.column
-
- @classmethod
- def from_column(cls, col: Column[Any]) -> AddColumnOp:
- return cls(col.table.name, col, schema=col.table.schema)
-
- @classmethod
- def from_column_and_tablename(
- cls,
- schema: Optional[str],
- tname: str,
- col: Column[Any],
- ) -> AddColumnOp:
- return cls(tname, col, schema=schema)
-
- @classmethod
- def add_column(
- cls,
- operations: Operations,
- table_name: str,
- column: Column[Any],
- *,
- schema: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- """Issue an "add column" instruction using the current
- migration context.
-
- e.g.::
-
- from alembic import op
- from sqlalchemy import Column, String
-
- op.add_column("organization", Column("name", String()))
-
- The :meth:`.Operations.add_column` method typically corresponds
- to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope
- of this command, the column's name, datatype, nullability,
- and optional server-generated defaults may be indicated.
-
- .. note::
-
- Not all contraint types may be indicated with this directive.
- PRIMARY KEY, NOT NULL, FOREIGN KEY, and CHECK are honored, UNIQUE
- is currently not.
-
- .. versionadded:: 1.18.2 Added support for PRIMARY KEY to be
- emitted within :meth:`.Operations.add_column`.
-
- As of 1.18.2, the following :class:`~sqlalchemy.schema.Column`
- parameters are **ignored**:
-
- * :paramref:`~sqlalchemy.schema.Column.unique` - use the
- :meth:`.Operations.create_unique_constraint` method
- * :paramref:`~sqlalchemy.schema.Column.index` - use the
- :meth:`.Operations.create_index` method
-
-
- The provided :class:`~sqlalchemy.schema.Column` object may include a
- :class:`~sqlalchemy.schema.ForeignKey` constraint directive,
- referencing a remote table name. By default, Alembic will automatically
- emit a second ALTER statement in order to add the single-column FOREIGN
- KEY constraint separately::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- )
-
- To render the FOREIGN KEY constraint inline within the ADD COLUMN
- directive, use the ``inline_references`` parameter. This can improve
- performance on large tables since the constraint is marked as valid
- immediately for nullable columns::
-
- from alembic import op
- from sqlalchemy import Column, INTEGER, ForeignKey
-
- op.add_column(
- "organization",
- Column("account_id", INTEGER, ForeignKey("accounts.id")),
- inline_references=True,
- )
-
- The column argument passed to :meth:`.Operations.add_column` is a
- :class:`~sqlalchemy.schema.Column` construct, used in the same way it's
- used in SQLAlchemy. In particular, values or functions to be indicated
- as producing the column's default value on the database side are
- specified using the ``server_default`` parameter, and not ``default``
- which only specifies Python-side defaults::
-
- from alembic import op
- from sqlalchemy import Column, TIMESTAMP, func
-
- # specify "DEFAULT NOW" along with the column add
- op.add_column(
- "account",
- Column("timestamp", TIMESTAMP, server_default=func.now()),
- )
-
- :param table_name: String name of the parent table.
- :param column: a :class:`sqlalchemy.schema.Column` object
- representing the new column.
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_not_exists: If True, adds IF NOT EXISTS operator
- when creating the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param inline_references: If True, renders FOREIGN KEY constraints
- inline within the ADD COLUMN directive using REFERENCES syntax,
- rather than as a separate ALTER TABLE ADD CONSTRAINT statement.
- This is supported by PostgreSQL, Oracle, MySQL 5.7+, and
- MariaDB 10.5+.
-
- .. versionadded:: 1.18.2
-
- """
-
- op = cls(
- table_name,
- column,
- schema=schema,
- if_not_exists=if_not_exists,
- inline_references=inline_references,
- )
- return operations.invoke(op)
-
- @classmethod
- def batch_add_column(
- cls,
- operations: BatchOperations,
- column: Column[Any],
- *,
- insert_before: Optional[str] = None,
- insert_after: Optional[str] = None,
- if_not_exists: Optional[bool] = None,
- inline_references: Optional[bool] = None,
- ) -> None:
- """Issue an "add column" instruction using the current
- batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.add_column`
-
- """
-
- kw = {}
- if insert_before:
- kw["insert_before"] = insert_before
- if insert_after:
- kw["insert_after"] = insert_after
-
- op = cls(
- operations.impl.table_name,
- column,
- schema=operations.impl.schema,
- if_not_exists=if_not_exists,
- inline_references=inline_references,
- **kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("drop_column")
-@BatchOperations.register_operation("drop_column", "batch_drop_column")
-class DropColumnOp(AlterTableOp):
- """Represent a drop column operation."""
-
- def __init__(
- self,
- table_name: str,
- column_name: str,
- *,
- schema: Optional[str] = None,
- if_exists: Optional[bool] = None,
- _reverse: Optional[AddColumnOp] = None,
- **kw: Any,
- ) -> None:
- super().__init__(table_name, schema=schema)
- self.column_name = column_name
- self.kw = kw
- self.if_exists = if_exists
- self._reverse = _reverse
-
- def to_diff_tuple(
- self,
- ) -> Tuple[str, Optional[str], str, Column[Any]]:
- return (
- "remove_column",
- self.schema,
- self.table_name,
- self.to_column(),
- )
-
- def reverse(self) -> AddColumnOp:
- if self._reverse is None:
- raise ValueError(
- "operation is not reversible; "
- "original column is not present"
- )
-
- op = AddColumnOp.from_column_and_tablename(
- self.schema, self.table_name, self._reverse.column
- )
- op.if_not_exists = self.if_exists
- return op
-
- @classmethod
- def from_column_and_tablename(
- cls,
- schema: Optional[str],
- tname: str,
- col: Column[Any],
- ) -> DropColumnOp:
- return cls(
- tname,
- col.name,
- schema=schema,
- _reverse=AddColumnOp.from_column_and_tablename(schema, tname, col),
- )
-
- def to_column(
- self, migration_context: Optional[MigrationContext] = None
- ) -> Column[Any]:
- if self._reverse is not None:
- return self._reverse.column
- schema_obj = schemaobj.SchemaObjects(migration_context)
- return schema_obj.column(self.column_name, NULLTYPE)
-
- @classmethod
- def drop_column(
- cls,
- operations: Operations,
- table_name: str,
- column_name: str,
- *,
- schema: Optional[str] = None,
- **kw: Any,
- ) -> None:
- """Issue a "drop column" instruction using the current
- migration context.
-
- e.g.::
-
- drop_column("organization", "account_id")
-
- :param table_name: name of table
- :param column_name: name of column
- :param schema: Optional schema name to operate within. To control
- quoting of the schema outside of the default behavior, use
- the SQLAlchemy construct
- :class:`~sqlalchemy.sql.elements.quoted_name`.
- :param if_exists: If True, adds IF EXISTS operator when
- dropping the new column for compatible dialects
-
- .. versionadded:: 1.16.0
-
- :param mssql_drop_check: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the CHECK constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.check_constraints,
- then exec's a separate DROP CONSTRAINT for that constraint.
- :param mssql_drop_default: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop the DEFAULT constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from sys.default_constraints,
- then exec's a separate DROP CONSTRAINT for that default.
- :param mssql_drop_foreign_key: Optional boolean. When ``True``, on
- Microsoft SQL Server only, first
- drop a single FOREIGN KEY constraint on the column using a
- SQL-script-compatible
- block that selects into a @variable from
- sys.foreign_keys/sys.foreign_key_columns,
- then exec's a separate DROP CONSTRAINT for that default. Only
- works if the column has exactly one FK constraint which refers to
- it, at the moment.
- """
-
- op = cls(table_name, column_name, schema=schema, **kw)
- return operations.invoke(op)
-
- @classmethod
- def batch_drop_column(
- cls, operations: BatchOperations, column_name: str, **kw: Any
- ) -> None:
- """Issue a "drop column" instruction using the current
- batch migration context.
-
- .. seealso::
-
- :meth:`.Operations.drop_column`
-
- """
- op = cls(
- operations.impl.table_name,
- column_name,
- schema=operations.impl.schema,
- **kw,
- )
- return operations.invoke(op)
-
-
-@Operations.register_operation("bulk_insert")
-class BulkInsertOp(MigrateOperation):
- """Represent a bulk insert operation."""
-
- def __init__(
- self,
- table: Union[Table, TableClause],
- rows: List[Dict[str, Any]],
- *,
- multiinsert: bool = True,
- ) -> None:
- self.table = table
- self.rows = rows
- self.multiinsert = multiinsert
-
- @classmethod
- def bulk_insert(
- cls,
- operations: Operations,
- table: Union[Table, TableClause],
- rows: List[Dict[str, Any]],
- *,
- multiinsert: bool = True,
- ) -> None:
- """Issue a "bulk insert" operation using the current
- migration context.
-
- This provides a means of representing an INSERT of multiple rows
- which works equally well in the context of executing on a live
- connection as well as that of generating a SQL script. In the
- case of a SQL script, the values are rendered inline into the
- statement.
-
- e.g.::
-
- from alembic import op
- from datetime import date
- from sqlalchemy.sql import table, column
- from sqlalchemy import String, Integer, Date
-
- # Create an ad-hoc table to use for the insert statement.
- accounts_table = table(
- "account",
- column("id", Integer),
- column("name", String),
- column("create_date", Date),
- )
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": date(2010, 10, 5),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": date(2007, 5, 27),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": date(2008, 8, 15),
- },
- ],
- )
-
- When using --sql mode, some datatypes may not render inline
- automatically, such as dates and other special types. When this
- issue is present, :meth:`.Operations.inline_literal` may be used::
-
- op.bulk_insert(
- accounts_table,
- [
- {
- "id": 1,
- "name": "John Smith",
- "create_date": op.inline_literal("2010-10-05"),
- },
- {
- "id": 2,
- "name": "Ed Williams",
- "create_date": op.inline_literal("2007-05-27"),
- },
- {
- "id": 3,
- "name": "Wendy Jones",
- "create_date": op.inline_literal("2008-08-15"),
- },
- ],
- multiinsert=False,
- )
-
- When using :meth:`.Operations.inline_literal` in conjunction with
- :meth:`.Operations.bulk_insert`, in order for the statement to work
- in "online" (e.g. non --sql) mode, the
- :paramref:`~.Operations.bulk_insert.multiinsert`
- flag should be set to ``False``, which will have the effect of
- individual INSERT statements being emitted to the database, each
- with a distinct VALUES clause, so that the "inline" values can
- still be rendered, rather than attempting to pass the values
- as bound parameters.
-
- :param table: a table object which represents the target of the INSERT.
-
- :param rows: a list of dictionaries indicating rows.
-
- :param multiinsert: when at its default of True and --sql mode is not
- enabled, the INSERT statement will be executed using
- "executemany()" style, where all elements in the list of
- dictionaries are passed as bound parameters in a single
- list. Setting this to False results in individual INSERT
- statements being emitted per parameter set, and is needed
- in those cases where non-literal values are present in the
- parameter sets.
-
- """
-
- op = cls(table, rows, multiinsert=multiinsert)
- operations.invoke(op)
-
-
-@Operations.register_operation("execute")
-@BatchOperations.register_operation("execute", "batch_execute")
-class ExecuteSQLOp(MigrateOperation):
- """Represent an execute SQL operation."""
-
- def __init__(
- self,
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- self.sqltext = sqltext
- self.execution_options = execution_options
-
- @classmethod
- def execute(
- cls,
- operations: Operations,
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- r"""Execute the given SQL using the current migration context.
-
- The given SQL can be a plain string, e.g.::
-
- op.execute("INSERT INTO table (foo) VALUES ('some value')")
-
- Or it can be any kind of Core SQL Expression construct, such as
- below where we use an update construct::
-
- from sqlalchemy.sql import table, column
- from sqlalchemy import String
- from alembic import op
-
- account = table("account", column("name", String))
- op.execute(
- account.update()
- .where(account.c.name == op.inline_literal("account 1"))
- .values({"name": op.inline_literal("account 2")})
- )
-
- Above, we made use of the SQLAlchemy
- :func:`sqlalchemy.sql.expression.table` and
- :func:`sqlalchemy.sql.expression.column` constructs to make a brief,
- ad-hoc table construct just for our UPDATE statement. A full
- :class:`~sqlalchemy.schema.Table` construct of course works perfectly
- fine as well, though note it's a recommended practice to at least
- ensure the definition of a table is self-contained within the migration
- script, rather than imported from a module that may break compatibility
- with older migrations.
-
- In a SQL script context, the statement is emitted directly to the
- output stream. There is *no* return result, however, as this
- function is oriented towards generating a change script
- that can run in "offline" mode. Additionally, parameterized
- statements are discouraged here, as they *will not work* in offline
- mode. Above, we use :meth:`.inline_literal` where parameters are
- to be used.
-
- For full interaction with a connected database where parameters can
- also be used normally, use the "bind" available from the context::
-
- from alembic import op
-
- connection = op.get_bind()
-
- connection.execute(
- account.update()
- .where(account.c.name == "account 1")
- .values({"name": "account 2"})
- )
-
- Additionally, when passing the statement as a plain string, it is first
- coerced into a :func:`sqlalchemy.sql.expression.text` construct
- before being passed along. In the less likely case that the
- literal SQL string contains a colon, it must be escaped with a
- backslash, as::
-
- op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')")
-
-
- :param sqltext: Any legal SQLAlchemy expression, including:
-
- * a string
- * a :func:`sqlalchemy.sql.expression.text` construct.
- * a :func:`sqlalchemy.sql.expression.insert` construct.
- * a :func:`sqlalchemy.sql.expression.update` construct.
- * a :func:`sqlalchemy.sql.expression.delete` construct.
- * Any "executable" described in SQLAlchemy Core documentation,
- noting that no result set is returned.
-
- .. note:: when passing a plain string, the statement is coerced into
- a :func:`sqlalchemy.sql.expression.text` construct. This construct
- considers symbols with colons, e.g. ``:foo`` to be bound parameters.
- To avoid this, ensure that colon symbols are escaped, e.g.
- ``\:foo``.
-
- :param execution_options: Optional dictionary of
- execution options, will be passed to
- :meth:`sqlalchemy.engine.Connection.execution_options`.
- """
- op = cls(sqltext, execution_options=execution_options)
- return operations.invoke(op)
-
- @classmethod
- def batch_execute(
- cls,
- operations: Operations,
- sqltext: Union[Executable, str],
- *,
- execution_options: Optional[dict[str, Any]] = None,
- ) -> None:
- """Execute the given SQL using the current migration context.
-
- .. seealso::
-
- :meth:`.Operations.execute`
-
- """
- return cls.execute(
- operations, sqltext, execution_options=execution_options
- )
-
- def to_diff_tuple(self) -> Tuple[str, Union[Executable, str]]:
- return ("execute", self.sqltext)
-
-
-class OpContainer(MigrateOperation):
- """Represent a sequence of operations operation."""
-
- def __init__(self, ops: Sequence[MigrateOperation] = ()) -> None:
- self.ops = list(ops)
-
- def is_empty(self) -> bool:
- return not self.ops
-
- def as_diffs(self) -> Any:
- return list(OpContainer._ops_as_diffs(self))
-
- @classmethod
- def _ops_as_diffs(
- cls, migrations: OpContainer
- ) -> Iterator[Tuple[Any, ...]]:
- for op in migrations.ops:
- if hasattr(op, "ops"):
- yield from cls._ops_as_diffs(cast("OpContainer", op))
- else:
- yield op.to_diff_tuple()
-
-
-class ModifyTableOps(OpContainer):
- """Contains a sequence of operations that all apply to a single Table."""
-
- def __init__(
- self,
- table_name: str,
- ops: Sequence[MigrateOperation],
- *,
- schema: Optional[str] = None,
- ) -> None:
- super().__init__(ops)
- self.table_name = table_name
- self.schema = schema
-
- def reverse(self) -> ModifyTableOps:
- return ModifyTableOps(
- self.table_name,
- ops=list(reversed([op.reverse() for op in self.ops])),
- schema=self.schema,
- )
-
-
-class UpgradeOps(OpContainer):
- """contains a sequence of operations that would apply to the
- 'upgrade' stream of a script.
-
- .. seealso::
-
- :ref:`customizing_revision`
-
- """
-
- def __init__(
- self,
- ops: Sequence[MigrateOperation] = (),
- upgrade_token: str = "upgrades",
- ) -> None:
- super().__init__(ops=ops)
- self.upgrade_token = upgrade_token
-
- def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps:
- downgrade_ops.ops[:] = list(
- reversed([op.reverse() for op in self.ops])
- )
- return downgrade_ops
-
- def reverse(self) -> DowngradeOps:
- return self.reverse_into(DowngradeOps(ops=[]))
-
-
-class DowngradeOps(OpContainer):
- """contains a sequence of operations that would apply to the
- 'downgrade' stream of a script.
-
- .. seealso::
-
- :ref:`customizing_revision`
-
- """
-
- def __init__(
- self,
- ops: Sequence[MigrateOperation] = (),
- downgrade_token: str = "downgrades",
- ) -> None:
- super().__init__(ops=ops)
- self.downgrade_token = downgrade_token
-
- def reverse(self) -> UpgradeOps:
- return UpgradeOps(
- ops=list(reversed([op.reverse() for op in self.ops]))
- )
-
-
-class MigrationScript(MigrateOperation):
- """represents a migration script.
-
- E.g. when autogenerate encounters this object, this corresponds to the
- production of an actual script file.
-
- A normal :class:`.MigrationScript` object would contain a single
- :class:`.UpgradeOps` and a single :class:`.DowngradeOps` directive.
- These are accessible via the ``.upgrade_ops`` and ``.downgrade_ops``
- attributes.
-
- In the case of an autogenerate operation that runs multiple times,
- such as the multiple database example in the "multidb" template,
- the ``.upgrade_ops`` and ``.downgrade_ops`` attributes are disabled,
- and instead these objects should be accessed via the ``.upgrade_ops_list``
- and ``.downgrade_ops_list`` list-based attributes. These latter
- attributes are always available at the very least as single-element lists.
-
- .. seealso::
-
- :ref:`customizing_revision`
-
- """
-
- _needs_render: Optional[bool]
- _upgrade_ops: List[UpgradeOps]
- _downgrade_ops: List[DowngradeOps]
-
- def __init__(
- self,
- rev_id: Optional[str],
- upgrade_ops: UpgradeOps,
- downgrade_ops: DowngradeOps,
- *,
- message: Optional[str] = None,
- imports: Set[str] = set(),
- head: Optional[str] = None,
- splice: Optional[bool] = None,
- branch_label: Optional[_RevIdType] = None,
- version_path: Union[str, os.PathLike[str], None] = None,
- depends_on: Optional[_RevIdType] = None,
- ) -> None:
- self.rev_id = rev_id
- self.message = message
- self.imports = imports
- self.head = head
- self.splice = splice
- self.branch_label = branch_label
- self.version_path = (
- pathlib.Path(version_path).as_posix() if version_path else None
- )
- self.depends_on = depends_on
- self.upgrade_ops = upgrade_ops
- self.downgrade_ops = downgrade_ops
-
- @property
- def upgrade_ops(self) -> Optional[UpgradeOps]:
- """An instance of :class:`.UpgradeOps`.
-
- .. seealso::
-
- :attr:`.MigrationScript.upgrade_ops_list`
- """
- if len(self._upgrade_ops) > 1:
- raise ValueError(
- "This MigrationScript instance has a multiple-entry "
- "list for UpgradeOps; please use the "
- "upgrade_ops_list attribute."
- )
- elif not self._upgrade_ops:
- return None
- else:
- return self._upgrade_ops[0]
-
- @upgrade_ops.setter
- def upgrade_ops(
- self, upgrade_ops: Union[UpgradeOps, List[UpgradeOps]]
- ) -> None:
- self._upgrade_ops = util.to_list(upgrade_ops)
- for elem in self._upgrade_ops:
- assert isinstance(elem, UpgradeOps)
-
- @property
- def downgrade_ops(self) -> Optional[DowngradeOps]:
- """An instance of :class:`.DowngradeOps`.
-
- .. seealso::
-
- :attr:`.MigrationScript.downgrade_ops_list`
- """
- if len(self._downgrade_ops) > 1:
- raise ValueError(
- "This MigrationScript instance has a multiple-entry "
- "list for DowngradeOps; please use the "
- "downgrade_ops_list attribute."
- )
- elif not self._downgrade_ops:
- return None
- else:
- return self._downgrade_ops[0]
-
- @downgrade_ops.setter
- def downgrade_ops(
- self, downgrade_ops: Union[DowngradeOps, List[DowngradeOps]]
- ) -> None:
- self._downgrade_ops = util.to_list(downgrade_ops)
- for elem in self._downgrade_ops:
- assert isinstance(elem, DowngradeOps)
-
- @property
- def upgrade_ops_list(self) -> List[UpgradeOps]:
- """A list of :class:`.UpgradeOps` instances.
-
- This is used in place of the :attr:`.MigrationScript.upgrade_ops`
- attribute when dealing with a revision operation that does
- multiple autogenerate passes.
-
- """
- return self._upgrade_ops
-
- @property
- def downgrade_ops_list(self) -> List[DowngradeOps]:
- """A list of :class:`.DowngradeOps` instances.
-
- This is used in place of the :attr:`.MigrationScript.downgrade_ops`
- attribute when dealing with a revision operation that does
- multiple autogenerate passes.
-
- """
- return self._downgrade_ops
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py
deleted file mode 100644
index 59c1002..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py
+++ /dev/null
@@ -1,290 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import schema as sa_schema
-from sqlalchemy.sql.schema import Column
-from sqlalchemy.sql.schema import Constraint
-from sqlalchemy.sql.schema import Index
-from sqlalchemy.types import Integer
-from sqlalchemy.types import NULLTYPE
-
-from .. import util
-from ..util import sqla_compat
-
-if TYPE_CHECKING:
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.elements import TextClause
- from sqlalchemy.sql.schema import CheckConstraint
- from sqlalchemy.sql.schema import ForeignKey
- from sqlalchemy.sql.schema import ForeignKeyConstraint
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import PrimaryKeyConstraint
- from sqlalchemy.sql.schema import Table
- from sqlalchemy.sql.schema import UniqueConstraint
- from sqlalchemy.sql.type_api import TypeEngine
-
- from ..runtime.migration import MigrationContext
-
-
-class SchemaObjects:
- def __init__(
- self, migration_context: Optional[MigrationContext] = None
- ) -> None:
- self.migration_context = migration_context
-
- def primary_key_constraint(
- self,
- name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- cols: Sequence[str],
- schema: Optional[str] = None,
- **dialect_kw,
- ) -> PrimaryKeyConstraint:
- m = self.metadata()
- columns = [sa_schema.Column(n, NULLTYPE) for n in cols]
- t = sa_schema.Table(table_name, m, *columns, schema=schema)
- # SQLAlchemy primary key constraint name arg is wrongly typed on
- # the SQLAlchemy side through 2.0.5 at least
- p = sa_schema.PrimaryKeyConstraint(
- *[t.c[n] for n in cols], name=name, **dialect_kw # type: ignore
- )
- return p
-
- def foreign_key_constraint(
- self,
- name: Optional[sqla_compat._ConstraintNameDefined],
- source: str,
- referent: str,
- local_cols: List[str],
- remote_cols: List[str],
- onupdate: Optional[str] = None,
- ondelete: Optional[str] = None,
- deferrable: Optional[bool] = None,
- source_schema: Optional[str] = None,
- referent_schema: Optional[str] = None,
- initially: Optional[str] = None,
- match: Optional[str] = None,
- **dialect_kw,
- ) -> ForeignKeyConstraint:
- m = self.metadata()
- if source == referent and source_schema == referent_schema:
- t1_cols = local_cols + remote_cols
- else:
- t1_cols = local_cols
- sa_schema.Table(
- referent,
- m,
- *[sa_schema.Column(n, NULLTYPE) for n in remote_cols],
- schema=referent_schema,
- )
-
- t1 = sa_schema.Table(
- source,
- m,
- *[
- sa_schema.Column(n, NULLTYPE)
- for n in util.unique_list(t1_cols)
- ],
- schema=source_schema,
- )
-
- tname = (
- "%s.%s" % (referent_schema, referent)
- if referent_schema
- else referent
- )
-
- dialect_kw["match"] = match
-
- f = sa_schema.ForeignKeyConstraint(
- local_cols,
- ["%s.%s" % (tname, n) for n in remote_cols],
- name=name,
- onupdate=onupdate,
- ondelete=ondelete,
- deferrable=deferrable,
- initially=initially,
- **dialect_kw,
- )
- t1.append_constraint(f)
-
- return f
-
- def unique_constraint(
- self,
- name: Optional[sqla_compat._ConstraintNameDefined],
- source: str,
- local_cols: Sequence[str],
- schema: Optional[str] = None,
- **kw,
- ) -> UniqueConstraint:
- t = sa_schema.Table(
- source,
- self.metadata(),
- *[sa_schema.Column(n, NULLTYPE) for n in local_cols],
- schema=schema,
- )
- kw["name"] = name
- uq = sa_schema.UniqueConstraint(*[t.c[n] for n in local_cols], **kw)
- # TODO: need event tests to ensure the event
- # is fired off here
- t.append_constraint(uq)
- return uq
-
- def check_constraint(
- self,
- name: Optional[sqla_compat._ConstraintNameDefined],
- source: str,
- condition: Union[str, TextClause, ColumnElement[Any]],
- schema: Optional[str] = None,
- **kw,
- ) -> Union[CheckConstraint]:
- t = sa_schema.Table(
- source,
- self.metadata(),
- sa_schema.Column("x", Integer),
- schema=schema,
- )
- ck = sa_schema.CheckConstraint(condition, name=name, **kw)
- t.append_constraint(ck)
- return ck
-
- def generic_constraint(
- self,
- name: Optional[sqla_compat._ConstraintNameDefined],
- table_name: str,
- type_: Optional[str],
- schema: Optional[str] = None,
- **kw,
- ) -> Any:
- t = self.table(table_name, schema=schema)
- types: Dict[Optional[str], Any] = {
- "foreignkey": lambda name: sa_schema.ForeignKeyConstraint(
- [], [], name=name
- ),
- "primary": sa_schema.PrimaryKeyConstraint,
- "unique": sa_schema.UniqueConstraint,
- "check": lambda name: sa_schema.CheckConstraint("", name=name),
- None: sa_schema.Constraint,
- }
- try:
- const = types[type_]
- except KeyError as ke:
- raise TypeError(
- "'type' can be one of %s"
- % ", ".join(sorted(repr(x) for x in types))
- ) from ke
- else:
- const = const(name=name)
- t.append_constraint(const)
- return const
-
- def metadata(self) -> MetaData:
- kw = {}
- if (
- self.migration_context is not None
- and "target_metadata" in self.migration_context.opts
- ):
- mt = self.migration_context.opts["target_metadata"]
- if hasattr(mt, "naming_convention"):
- kw["naming_convention"] = mt.naming_convention
- return sa_schema.MetaData(**kw)
-
- def table(self, name: str, *columns, **kw) -> Table:
- m = self.metadata()
-
- cols = [
- sqla_compat._copy(c) if c.table is not None else c
- for c in columns
- if isinstance(c, Column)
- ]
- # these flags have already added their UniqueConstraint /
- # Index objects to the table, so flip them off here.
- # SQLAlchemy tometadata() avoids this instead by preserving the
- # flags and skipping the constraints that have _type_bound on them,
- # but for a migration we'd rather list out the constraints
- # explicitly.
- _constraints_included = kw.pop("_constraints_included", False)
- if _constraints_included:
- for c in cols:
- c.unique = c.index = False
-
- t = sa_schema.Table(name, m, *cols, **kw)
-
- constraints = [
- (
- sqla_compat._copy(elem, target_table=t)
- if getattr(elem, "parent", None) is not t
- and getattr(elem, "parent", None) is not None
- else elem
- )
- for elem in columns
- if isinstance(elem, (Constraint, Index))
- ]
-
- for const in constraints:
- t.append_constraint(const)
-
- for f in t.foreign_keys:
- self._ensure_table_for_fk(m, f)
- return t
-
- def column(self, name: str, type_: TypeEngine, **kw) -> Column:
- return sa_schema.Column(name, type_, **kw)
-
- def index(
- self,
- name: Optional[str],
- tablename: Optional[str],
- columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
- schema: Optional[str] = None,
- **kw,
- ) -> Index:
- t = sa_schema.Table(
- tablename or "no_table",
- self.metadata(),
- schema=schema,
- )
- kw["_table"] = t
- idx = sa_schema.Index(
- name,
- *[util.sqla_compat._textual_index_column(t, n) for n in columns],
- **kw,
- )
- return idx
-
- def _parse_table_key(self, table_key: str) -> Tuple[Optional[str], str]:
- if "." in table_key:
- tokens = table_key.split(".")
- sname: Optional[str] = ".".join(tokens[0:-1])
- tname = tokens[-1]
- else:
- tname = table_key
- sname = None
- return (sname, tname)
-
- def _ensure_table_for_fk(self, metadata: MetaData, fk: ForeignKey) -> None:
- """create a placeholder Table object for the referent of a
- ForeignKey.
-
- """
- if isinstance(fk._colspec, str):
- table_key, cname = fk._colspec.rsplit(".", 1)
- sname, tname = self._parse_table_key(table_key)
- if table_key not in metadata.tables:
- rel_t = sa_schema.Table(tname, metadata, schema=sname)
- else:
- rel_t = metadata.tables[table_key]
- if cname not in rel_t.c:
- rel_t.append_column(sa_schema.Column(cname, NULLTYPE))
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/operations/toimpl.py b/backend/.venv/lib/python3.12/site-packages/alembic/operations/toimpl.py
deleted file mode 100644
index b9b4b71..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/operations/toimpl.py
+++ /dev/null
@@ -1,259 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from typing import TYPE_CHECKING
-
-from sqlalchemy import schema as sa_schema
-
-from . import ops
-from .base import Operations
-from ..util.sqla_compat import _copy
-from ..util.sqla_compat import sqla_2
-
-if TYPE_CHECKING:
- from sqlalchemy.sql.schema import Table
-
-
-@Operations.implementation_for(ops.AlterColumnOp)
-def alter_column(
- operations: "Operations", operation: "ops.AlterColumnOp"
-) -> None:
- compiler = operations.impl.dialect.statement_compiler(
- operations.impl.dialect, None
- )
-
- existing_type = operation.existing_type
- existing_nullable = operation.existing_nullable
- existing_server_default = operation.existing_server_default
- type_ = operation.modify_type
- column_name = operation.column_name
- table_name = operation.table_name
- schema = operation.schema
- server_default = operation.modify_server_default
- new_column_name = operation.modify_name
- nullable = operation.modify_nullable
- comment = operation.modify_comment
- existing_comment = operation.existing_comment
-
- def _count_constraint(constraint):
- return not isinstance(constraint, sa_schema.PrimaryKeyConstraint) and (
- not constraint._create_rule or constraint._create_rule(compiler)
- )
-
- if existing_type and type_:
- t = operations.schema_obj.table(
- table_name,
- sa_schema.Column(column_name, existing_type),
- schema=schema,
- )
- for constraint in t.constraints:
- if _count_constraint(constraint):
- operations.impl.drop_constraint(constraint)
-
- # some weird pyright quirk here, these have Literal[False]
- # in their types, not sure why pyright thinks they could be True
- assert existing_server_default is not True # type: ignore[comparison-overlap] # noqa: E501
- assert comment is not True # type: ignore[comparison-overlap]
-
- operations.impl.alter_column(
- table_name,
- column_name,
- nullable=nullable,
- server_default=server_default,
- name=new_column_name,
- type_=type_,
- schema=schema,
- existing_type=existing_type,
- existing_server_default=existing_server_default,
- existing_nullable=existing_nullable,
- comment=comment,
- existing_comment=existing_comment,
- **operation.kw,
- )
-
- if type_:
- t = operations.schema_obj.table(
- table_name,
- operations.schema_obj.column(column_name, type_),
- schema=schema,
- )
- for constraint in t.constraints:
- if _count_constraint(constraint):
- operations.impl.add_constraint(constraint)
-
-
-@Operations.implementation_for(ops.DropTableOp)
-def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None:
- kw = {}
- if operation.if_exists is not None:
- kw["if_exists"] = operation.if_exists
- operations.impl.drop_table(
- operation.to_table(operations.migration_context), **kw
- )
-
-
-@Operations.implementation_for(ops.DropColumnOp)
-def drop_column(
- operations: "Operations", operation: "ops.DropColumnOp"
-) -> None:
- column = operation.to_column(operations.migration_context)
- operations.impl.drop_column(
- operation.table_name,
- column,
- schema=operation.schema,
- if_exists=operation.if_exists,
- **operation.kw,
- )
-
-
-@Operations.implementation_for(ops.CreateIndexOp)
-def create_index(
- operations: "Operations", operation: "ops.CreateIndexOp"
-) -> None:
- idx = operation.to_index(operations.migration_context)
- kw = {}
- if operation.if_not_exists is not None:
- kw["if_not_exists"] = operation.if_not_exists
- operations.impl.create_index(idx, **kw)
-
-
-@Operations.implementation_for(ops.DropIndexOp)
-def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
- kw = {}
- if operation.if_exists is not None:
- kw["if_exists"] = operation.if_exists
-
- operations.impl.drop_index(
- operation.to_index(operations.migration_context),
- **kw,
- )
-
-
-@Operations.implementation_for(ops.CreateTableOp)
-def create_table(
- operations: "Operations", operation: "ops.CreateTableOp"
-) -> "Table":
- kw = {}
- if operation.if_not_exists is not None:
- kw["if_not_exists"] = operation.if_not_exists
- table = operation.to_table(operations.migration_context)
- operations.impl.create_table(table, **kw)
- return table
-
-
-@Operations.implementation_for(ops.RenameTableOp)
-def rename_table(
- operations: "Operations", operation: "ops.RenameTableOp"
-) -> None:
- operations.impl.rename_table(
- operation.table_name, operation.new_table_name, schema=operation.schema
- )
-
-
-@Operations.implementation_for(ops.CreateTableCommentOp)
-def create_table_comment(
- operations: "Operations", operation: "ops.CreateTableCommentOp"
-) -> None:
- table = operation.to_table(operations.migration_context)
- operations.impl.create_table_comment(table)
-
-
-@Operations.implementation_for(ops.DropTableCommentOp)
-def drop_table_comment(
- operations: "Operations", operation: "ops.DropTableCommentOp"
-) -> None:
- table = operation.to_table(operations.migration_context)
- operations.impl.drop_table_comment(table)
-
-
-@Operations.implementation_for(ops.AddColumnOp)
-def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
- table_name = operation.table_name
- column = operation.column
- schema = operation.schema
- kw = operation.kw
- inline_references = operation.inline_references
-
- if column.table is not None:
- column = _copy(column)
-
- t = operations.schema_obj.table(table_name, column, schema=schema)
- operations.impl.add_column(
- table_name,
- column,
- schema=schema,
- if_not_exists=operation.if_not_exists,
- inline_references=inline_references,
- **kw,
- )
-
- for constraint in t.constraints:
- if not isinstance(constraint, sa_schema.PrimaryKeyConstraint):
- # Skip ForeignKeyConstraint if it was rendered inline
- # This only happens when inline_references=True AND there's exactly
- # one FK AND the constraint is single-column
- if (
- inline_references
- and isinstance(constraint, sa_schema.ForeignKeyConstraint)
- and len(column.foreign_keys) == 1
- and len(constraint.columns) == 1
- ):
- continue
- operations.impl.add_constraint(constraint)
- for index in t.indexes:
- operations.impl.create_index(index)
-
- with_comment = (
- operations.impl.dialect.supports_comments
- and not operations.impl.dialect.inline_comments
- )
- comment = column.comment
- if comment and with_comment:
- operations.impl.create_column_comment(column)
-
-
-@Operations.implementation_for(ops.AddConstraintOp)
-def create_constraint(
- operations: "Operations", operation: "ops.AddConstraintOp"
-) -> None:
- operations.impl.add_constraint(
- operation.to_constraint(operations.migration_context)
- )
-
-
-@Operations.implementation_for(ops.DropConstraintOp)
-def drop_constraint(
- operations: "Operations", operation: "ops.DropConstraintOp"
-) -> None:
- kw = {}
- if operation.if_exists is not None:
- if not sqla_2:
- raise NotImplementedError("SQLAlchemy 2.0 required")
- kw["if_exists"] = operation.if_exists
- operations.impl.drop_constraint(
- operations.schema_obj.generic_constraint(
- operation.constraint_name,
- operation.table_name,
- operation.constraint_type,
- schema=operation.schema,
- ),
- **kw,
- )
-
-
-@Operations.implementation_for(ops.BulkInsertOp)
-def bulk_insert(
- operations: "Operations", operation: "ops.BulkInsertOp"
-) -> None:
- operations.impl.bulk_insert( # type: ignore[union-attr]
- operation.table, operation.rows, multiinsert=operation.multiinsert
- )
-
-
-@Operations.implementation_for(ops.ExecuteSQLOp)
-def execute_sql(
- operations: "Operations", operation: "ops.ExecuteSQLOp"
-) -> None:
- operations.migration_context.impl.execute(
- operation.sqltext, execution_options=operation.execution_options
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/py.typed b/backend/.venv/lib/python3.12/site-packages/alembic/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index f7b2f7a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc
deleted file mode 100644
index cd281d0..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc
deleted file mode 100644
index f9fccb6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/plugins.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/plugins.cpython-312.pyc
deleted file mode 100644
index 7a9d6b6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/plugins.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/environment.py b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/environment.py
deleted file mode 100644
index 5817e2d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/environment.py
+++ /dev/null
@@ -1,1074 +0,0 @@
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import Collection
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import MutableMapping
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import TextIO
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy.sql.schema import Column
-from sqlalchemy.sql.schema import FetchedValue
-from typing_extensions import ContextManager
-from typing_extensions import Literal
-
-from .migration import _ProxyTransaction
-from .migration import MigrationContext
-from .. import util
-from ..operations import Operations
-from ..script.revision import _GetRevArg
-
-if TYPE_CHECKING:
- from sqlalchemy.engine import URL
- from sqlalchemy.engine.base import Connection
- from sqlalchemy.sql import Executable
- from sqlalchemy.sql.schema import MetaData
- from sqlalchemy.sql.schema import SchemaItem
- from sqlalchemy.sql.type_api import TypeEngine
-
- from .migration import MigrationInfo
- from ..autogenerate.api import AutogenContext
- from ..config import Config
- from ..ddl import DefaultImpl
- from ..operations.ops import MigrationScript
- from ..script.base import ScriptDirectory
-
-_RevNumber = Optional[Union[str, Tuple[str, ...]]]
-
-ProcessRevisionDirectiveFn = Callable[
- [MigrationContext, _GetRevArg, List["MigrationScript"]], None
-]
-
-RenderItemFn = Callable[
- [str, Any, "AutogenContext"], Union[str, Literal[False]]
-]
-
-NameFilterType = Literal[
- "schema",
- "table",
- "column",
- "index",
- "unique_constraint",
- "foreign_key_constraint",
-]
-NameFilterParentNames = MutableMapping[
- Literal["schema_name", "table_name", "schema_qualified_table_name"],
- Optional[str],
-]
-IncludeNameFn = Callable[
- [Optional[str], NameFilterType, NameFilterParentNames], bool
-]
-
-IncludeObjectFn = Callable[
- [
- "SchemaItem",
- Optional[str],
- NameFilterType,
- bool,
- Optional["SchemaItem"],
- ],
- bool,
-]
-
-OnVersionApplyFn = Callable[
- [MigrationContext, "MigrationInfo", Collection[Any], Mapping[str, Any]],
- None,
-]
-
-CompareServerDefault = Callable[
- [
- MigrationContext,
- "Column[Any]",
- "Column[Any]",
- Optional[str],
- Optional[FetchedValue],
- Optional[str],
- ],
- Optional[bool],
-]
-
-CompareType = Callable[
- [
- MigrationContext,
- "Column[Any]",
- "Column[Any]",
- "TypeEngine[Any]",
- "TypeEngine[Any]",
- ],
- Optional[bool],
-]
-
-
-class EnvironmentContext(util.ModuleClsProxy):
- """A configurational facade made available in an ``env.py`` script.
-
- The :class:`.EnvironmentContext` acts as a *facade* to the more
- nuts-and-bolts objects of :class:`.MigrationContext` as well as certain
- aspects of :class:`.Config`,
- within the context of the ``env.py`` script that is invoked by
- most Alembic commands.
-
- :class:`.EnvironmentContext` is normally instantiated
- when a command in :mod:`alembic.command` is run. It then makes
- itself available in the ``alembic.context`` module for the scope
- of the command. From within an ``env.py`` script, the current
- :class:`.EnvironmentContext` is available by importing this module.
-
- :class:`.EnvironmentContext` also supports programmatic usage.
- At this level, it acts as a Python context manager, that is, is
- intended to be used using the
- ``with:`` statement. A typical use of :class:`.EnvironmentContext`::
-
- from alembic.config import Config
- from alembic.script import ScriptDirectory
-
- config = Config()
- config.set_main_option("script_location", "myapp:migrations")
- script = ScriptDirectory.from_config(config)
-
-
- def my_function(rev, context):
- '''do something with revision "rev", which
- will be the current database revision,
- and "context", which is the MigrationContext
- that the env.py will create'''
-
-
- with EnvironmentContext(
- config,
- script,
- fn=my_function,
- as_sql=False,
- starting_rev="base",
- destination_rev="head",
- tag="sometag",
- ):
- script.run_env()
-
- The above script will invoke the ``env.py`` script
- within the migration environment. If and when ``env.py``
- calls :meth:`.MigrationContext.run_migrations`, the
- ``my_function()`` function above will be called
- by the :class:`.MigrationContext`, given the context
- itself as well as the current revision in the database.
-
- .. note::
-
- For most API usages other than full blown
- invocation of migration scripts, the :class:`.MigrationContext`
- and :class:`.ScriptDirectory` objects can be created and
- used directly. The :class:`.EnvironmentContext` object
- is *only* needed when you need to actually invoke the
- ``env.py`` module present in the migration environment.
-
- """
-
- _migration_context: Optional[MigrationContext] = None
-
- config: Config = None # type:ignore[assignment]
- """An instance of :class:`.Config` representing the
- configuration file contents as well as other variables
- set programmatically within it."""
-
- script: ScriptDirectory = None # type:ignore[assignment]
- """An instance of :class:`.ScriptDirectory` which provides
- programmatic access to version files within the ``versions/``
- directory.
-
- """
-
- def __init__(
- self, config: Config, script: ScriptDirectory, **kw: Any
- ) -> None:
- r"""Construct a new :class:`.EnvironmentContext`.
-
- :param config: a :class:`.Config` instance.
- :param script: a :class:`.ScriptDirectory` instance.
- :param \**kw: keyword options that will be ultimately
- passed along to the :class:`.MigrationContext` when
- :meth:`.EnvironmentContext.configure` is called.
-
- """
- self.config = config
- self.script = script
- self.context_opts = kw
-
- def __enter__(self) -> EnvironmentContext:
- """Establish a context which provides a
- :class:`.EnvironmentContext` object to
- env.py scripts.
-
- The :class:`.EnvironmentContext` will
- be made available as ``from alembic import context``.
-
- """
- self._install_proxy()
- return self
-
- def __exit__(self, *arg: Any, **kw: Any) -> None:
- self._remove_proxy()
-
- def is_offline_mode(self) -> bool:
- """Return True if the current migrations environment
- is running in "offline mode".
-
- This is ``True`` or ``False`` depending
- on the ``--sql`` flag passed.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
- return self.context_opts.get("as_sql", False) # type: ignore[no-any-return] # noqa: E501
-
- def is_transactional_ddl(self) -> bool:
- """Return True if the context is configured to expect a
- transactional DDL capable backend.
-
- This defaults to the type of database in use, and
- can be overridden by the ``transactional_ddl`` argument
- to :meth:`.configure`
-
- This function requires that a :class:`.MigrationContext`
- has first been made available via :meth:`.configure`.
-
- """
- return self.get_context().impl.transactional_ddl
-
- def requires_connection(self) -> bool:
- return not self.is_offline_mode()
-
- def get_head_revision(self) -> _RevNumber:
- """Return the hex identifier of the 'head' script revision.
-
- If the script directory has multiple heads, this
- method raises a :class:`.CommandError`;
- :meth:`.EnvironmentContext.get_head_revisions` should be preferred.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso:: :meth:`.EnvironmentContext.get_head_revisions`
-
- """
- return self.script.as_revision_number("head")
-
- def get_head_revisions(self) -> _RevNumber:
- """Return the hex identifier of the 'heads' script revision(s).
-
- This returns a tuple containing the version number of all
- heads in the script directory.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
- return self.script.as_revision_number("heads")
-
- def get_starting_revision_argument(self) -> _RevNumber:
- """Return the 'starting revision' argument,
- if the revision was passed using ``start:end``.
-
- This is only meaningful in "offline" mode.
- Returns ``None`` if no value is available
- or was configured.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
- if self._migration_context is not None:
- return self.script.as_revision_number(
- self.get_context()._start_from_rev
- )
- elif "starting_rev" in self.context_opts:
- return self.script.as_revision_number(
- self.context_opts["starting_rev"]
- )
- else:
- # this should raise only in the case that a command
- # is being run where the "starting rev" is never applicable;
- # this is to catch scripts which rely upon this in
- # non-sql mode or similar
- raise util.CommandError(
- "No starting revision argument is available."
- )
-
- def get_revision_argument(self) -> _RevNumber:
- """Get the 'destination' revision argument.
-
- This is typically the argument passed to the
- ``upgrade`` or ``downgrade`` command.
-
- If it was specified as ``head``, the actual
- version number is returned; if specified
- as ``base``, ``None`` is returned.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- """
- return self.script.as_revision_number(
- self.context_opts["destination_rev"]
- )
-
- def get_tag_argument(self) -> Optional[str]:
- """Return the value passed for the ``--tag`` argument, if any.
-
- The ``--tag`` argument is not used directly by Alembic,
- but is available for custom ``env.py`` configurations that
- wish to use it; particularly for offline generation scripts
- that wish to generate tagged filenames.
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso::
-
- :meth:`.EnvironmentContext.get_x_argument` - a newer and more
- open ended system of extending ``env.py`` scripts via the command
- line.
-
- """
- return self.context_opts.get("tag", None)
-
- @overload
- def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]: ...
-
- @overload
- def get_x_argument(
- self, as_dictionary: Literal[True]
- ) -> Dict[str, str]: ...
-
- @overload
- def get_x_argument(
- self, as_dictionary: bool = ...
- ) -> Union[List[str], Dict[str, str]]: ...
-
- def get_x_argument(
- self, as_dictionary: bool = False
- ) -> Union[List[str], Dict[str, str]]:
- """Return the value(s) passed for the ``-x`` argument, if any.
-
- The ``-x`` argument is an open ended flag that allows any user-defined
- value or values to be passed on the command line, then available
- here for consumption by a custom ``env.py`` script.
-
- The return value is a list, returned directly from the ``argparse``
- structure. If ``as_dictionary=True`` is passed, the ``x`` arguments
- are parsed using ``key=value`` format into a dictionary that is
- then returned. If there is no ``=`` in the argument, value is an empty
- string.
-
- .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when
- arguments are passed without the ``=`` symbol.
-
- For example, to support passing a database URL on the command line,
- the standard ``env.py`` script can be modified like this::
-
- cmd_line_url = context.get_x_argument(
- as_dictionary=True).get('dbname')
- if cmd_line_url:
- engine = create_engine(cmd_line_url)
- else:
- engine = engine_from_config(
- config.get_section(config.config_ini_section),
- prefix='sqlalchemy.',
- poolclass=pool.NullPool)
-
- This then takes effect by running the ``alembic`` script as::
-
- alembic -x dbname=postgresql://user:pass@host/dbname upgrade head
-
- This function does not require that the :class:`.MigrationContext`
- has been configured.
-
- .. seealso::
-
- :meth:`.EnvironmentContext.get_tag_argument`
-
- :attr:`.Config.cmd_opts`
-
- """
- if self.config.cmd_opts is not None:
- value = self.config.cmd_opts.x or []
- else:
- value = []
- if as_dictionary:
- dict_value = {}
- for arg in value:
- x_key, _, x_value = arg.partition("=")
- dict_value[x_key] = x_value
- value = dict_value
-
- return value
-
- def configure(
- self,
- connection: Optional[Connection] = None,
- url: Optional[Union[str, URL]] = None,
- dialect_name: Optional[str] = None,
- dialect_opts: Optional[Dict[str, Any]] = None,
- transactional_ddl: Optional[bool] = None,
- transaction_per_migration: bool = False,
- output_buffer: Optional[TextIO] = None,
- starting_rev: Optional[str] = None,
- tag: Optional[str] = None,
- template_args: Optional[Dict[str, Any]] = None,
- render_as_batch: bool = False,
- target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
- include_name: Optional[IncludeNameFn] = None,
- include_object: Optional[IncludeObjectFn] = None,
- include_schemas: bool = False,
- process_revision_directives: Optional[
- ProcessRevisionDirectiveFn
- ] = None,
- compare_type: Union[bool, CompareType] = True,
- compare_server_default: Union[bool, CompareServerDefault] = False,
- render_item: Optional[RenderItemFn] = None,
- literal_binds: bool = False,
- upgrade_token: str = "upgrades",
- downgrade_token: str = "downgrades",
- alembic_module_prefix: str = "op.",
- sqlalchemy_module_prefix: str = "sa.",
- user_module_prefix: Optional[str] = None,
- on_version_apply: Optional[OnVersionApplyFn] = None,
- autogenerate_plugins: Sequence[str] | None = None,
- **kw: Any,
- ) -> None:
- """Configure a :class:`.MigrationContext` within this
- :class:`.EnvironmentContext` which will provide database
- connectivity and other configuration to a series of
- migration scripts.
-
- Many methods on :class:`.EnvironmentContext` require that
- this method has been called in order to function, as they
- ultimately need to have database access or at least access
- to the dialect in use. Those which do are documented as such.
-
- The important thing needed by :meth:`.configure` is a
- means to determine what kind of database dialect is in use.
- An actual connection to that database is needed only if
- the :class:`.MigrationContext` is to be used in
- "online" mode.
-
- If the :meth:`.is_offline_mode` function returns ``True``,
- then no connection is needed here. Otherwise, the
- ``connection`` parameter should be present as an
- instance of :class:`sqlalchemy.engine.Connection`.
-
- This function is typically called from the ``env.py``
- script within a migration environment. It can be called
- multiple times for an invocation. The most recent
- :class:`~sqlalchemy.engine.Connection`
- for which it was called is the one that will be operated upon
- by the next call to :meth:`.run_migrations`.
-
- General parameters:
-
- :param connection: a :class:`~sqlalchemy.engine.Connection`
- to use
- for SQL execution in "online" mode. When present, is also
- used to determine the type of dialect in use.
- :param url: a string database url, or a
- :class:`sqlalchemy.engine.url.URL` object.
- The type of dialect to be used will be derived from this if
- ``connection`` is not passed.
- :param dialect_name: string name of a dialect, such as
- "postgresql", "mssql", etc.
- The type of dialect to be used will be derived from this if
- ``connection`` and ``url`` are not passed.
- :param dialect_opts: dictionary of options to be passed to dialect
- constructor.
- :param transactional_ddl: Force the usage of "transactional"
- DDL on or off;
- this otherwise defaults to whether or not the dialect in
- use supports it.
- :param transaction_per_migration: if True, nest each migration script
- in a transaction rather than the full series of migrations to
- run.
- :param output_buffer: a file-like object that will be used
- for textual output
- when the ``--sql`` option is used to generate SQL scripts.
- Defaults to
- ``sys.stdout`` if not passed here and also not present on
- the :class:`.Config`
- object. The value here overrides that of the :class:`.Config`
- object.
- :param output_encoding: when using ``--sql`` to generate SQL
- scripts, apply this encoding to the string output.
- :param literal_binds: when using ``--sql`` to generate SQL
- scripts, pass through the ``literal_binds`` flag to the compiler
- so that any literal values that would ordinarily be bound
- parameters are converted to plain strings.
-
- .. warning:: Dialects can typically only handle simple datatypes
- like strings and numbers for auto-literal generation. Datatypes
- like dates, intervals, and others may still require manual
- formatting, typically using :meth:`.Operations.inline_literal`.
-
- .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy
- versions prior to 0.8 where this feature is not supported.
-
- .. seealso::
-
- :meth:`.Operations.inline_literal`
-
- :param starting_rev: Override the "starting revision" argument
- when using ``--sql`` mode.
- :param tag: a string tag for usage by custom ``env.py`` scripts.
- Set via the ``--tag`` option, can be overridden here.
- :param template_args: dictionary of template arguments which
- will be added to the template argument environment when
- running the "revision" command. Note that the script environment
- is only run within the "revision" command if the --autogenerate
- option is used, or if the option "revision_environment=true"
- is present in the alembic.ini file.
-
- :param version_table: The name of the Alembic version table.
- The default is ``'alembic_version'``.
- :param version_table_schema: Optional schema to place version
- table within.
- :param version_table_pk: boolean, whether the Alembic version table
- should use a primary key constraint for the "value" column; this
- only takes effect when the table is first created.
- Defaults to True; setting to False should not be necessary and is
- here for backwards compatibility reasons.
- :param on_version_apply: a callable or collection of callables to be
- run for each migration step.
- The callables will be run in the order they are given, once for
- each migration step, after the respective operation has been
- applied but before its transaction is finalized.
- Each callable accepts no positional arguments and the following
- keyword arguments:
-
- * ``ctx``: the :class:`.MigrationContext` running the migration,
- * ``step``: a :class:`.MigrationInfo` representing the
- step currently being applied,
- * ``heads``: a collection of version strings representing the
- current heads,
- * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`.
-
- Parameters specific to the autogenerate feature, when
- ``alembic revision`` is run with the ``--autogenerate`` feature:
-
- :param target_metadata: a :class:`sqlalchemy.schema.MetaData`
- object, or a sequence of :class:`~sqlalchemy.schema.MetaData`
- objects, that will be consulted during autogeneration.
- The tables present in each :class:`~sqlalchemy.schema.MetaData`
- will be compared against
- what is locally available on the target
- :class:`~sqlalchemy.engine.Connection`
- to produce candidate upgrade/downgrade operations.
- :param compare_type: Indicates type comparison behavior during
- an autogenerate
- operation. Defaults to ``True`` turning on type comparison, which
- has good accuracy on most backends. See :ref:`compare_types`
- for an example as well as information on other type
- comparison options. Set to ``False`` which disables type
- comparison. A callable can also be passed to provide custom type
- comparison, see :ref:`compare_types` for additional details.
-
- .. versionchanged:: 1.12.0 The default value of
- :paramref:`.EnvironmentContext.configure.compare_type` has been
- changed to ``True``.
-
- .. seealso::
-
- :ref:`compare_types`
-
- :paramref:`.EnvironmentContext.configure.compare_server_default`
-
- :param compare_server_default: Indicates server default comparison
- behavior during
- an autogenerate operation. Defaults to ``False`` which disables
- server default
- comparison. Set to ``True`` to turn on server default comparison,
- which has
- varied accuracy depending on backend.
-
- To customize server default comparison behavior, a callable may
- be specified
- which can filter server default comparisons during an
- autogenerate operation.
- defaults during an autogenerate operation. The format of this
- callable is::
-
- def my_compare_server_default(context, inspected_column,
- metadata_column, inspected_default, metadata_default,
- rendered_metadata_default):
- # return True if the defaults are different,
- # False if not, or None to allow the default implementation
- # to compare these defaults
- return None
-
- context.configure(
- # ...
- compare_server_default = my_compare_server_default
- )
-
- ``inspected_column`` is a dictionary structure as returned by
- :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas
- ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from
- the local model environment.
-
- A return value of ``None`` indicates to allow default server default
- comparison
- to proceed. Note that some backends such as Postgresql actually
- execute
- the two defaults on the database side to compare for equivalence.
-
- .. seealso::
-
- :paramref:`.EnvironmentContext.configure.compare_type`
-
- :param include_name: A callable function which is given
- the chance to return ``True`` or ``False`` for any database reflected
- object based on its name, including database schema names when
- the :paramref:`.EnvironmentContext.configure.include_schemas` flag
- is set to ``True``.
-
- The function accepts the following positional arguments:
-
- * ``name``: the name of the object, such as schema name or table name.
- Will be ``None`` when indicating the default schema name of the
- database connection.
- * ``type``: a string describing the type of object; currently
- ``"schema"``, ``"table"``, ``"column"``, ``"index"``,
- ``"unique_constraint"``, or ``"foreign_key_constraint"``
- * ``parent_names``: a dictionary of "parent" object names, that are
- relative to the name being given. Keys in this dictionary may
- include: ``"schema_name"``, ``"table_name"`` or
- ``"schema_qualified_table_name"``.
-
- E.g.::
-
- def include_name(name, type_, parent_names):
- if type_ == "schema":
- return name in ["schema_one", "schema_two"]
- else:
- return True
-
- context.configure(
- # ...
- include_schemas = True,
- include_name = include_name
- )
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_object`
-
- :paramref:`.EnvironmentContext.configure.include_schemas`
-
-
- :param include_object: A callable function which is given
- the chance to return ``True`` or ``False`` for any object,
- indicating if the given object should be considered in the
- autogenerate sweep.
-
- The function accepts the following positional arguments:
-
- * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such
- as a :class:`~sqlalchemy.schema.Table`,
- :class:`~sqlalchemy.schema.Column`,
- :class:`~sqlalchemy.schema.Index`
- :class:`~sqlalchemy.schema.UniqueConstraint`,
- or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object
- * ``name``: the name of the object. This is typically available
- via ``object.name``.
- * ``type``: a string describing the type of object; currently
- ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``,
- or ``"foreign_key_constraint"``
- * ``reflected``: ``True`` if the given object was produced based on
- table reflection, ``False`` if it's from a local :class:`.MetaData`
- object.
- * ``compare_to``: the object being compared against, if available,
- else ``None``.
-
- E.g.::
-
- def include_object(object, name, type_, reflected, compare_to):
- if (type_ == "column" and
- not reflected and
- object.info.get("skip_autogenerate", False)):
- return False
- else:
- return True
-
- context.configure(
- # ...
- include_object = include_object
- )
-
- For the use case of omitting specific schemas from a target database
- when :paramref:`.EnvironmentContext.configure.include_schemas` is
- set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema`
- attribute can be checked for each :class:`~sqlalchemy.schema.Table`
- object passed to the hook, however it is much more efficient
- to filter on schemas before reflection of objects takes place
- using the :paramref:`.EnvironmentContext.configure.include_name`
- hook.
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_name`
-
- :paramref:`.EnvironmentContext.configure.include_schemas`
-
- :param render_as_batch: if True, commands which alter elements
- within a table will be placed under a ``with batch_alter_table():``
- directive, so that batch migrations will take place.
-
- .. seealso::
-
- :ref:`batch_migrations`
-
- :param include_schemas: If True, autogenerate will scan across
- all schemas located by the SQLAlchemy
- :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names`
- method, and include all differences in tables found across all
- those schemas. When using this option, you may want to also
- use the :paramref:`.EnvironmentContext.configure.include_name`
- parameter to specify a callable which
- can filter the tables/schemas that get included.
-
- .. seealso::
-
- :ref:`autogenerate_include_hooks`
-
- :paramref:`.EnvironmentContext.configure.include_name`
-
- :paramref:`.EnvironmentContext.configure.include_object`
-
- :param render_item: Callable that can be used to override how
- any schema item, i.e. column, constraint, type,
- etc., is rendered for autogenerate. The callable receives a
- string describing the type of object, the object, and
- the autogen context. If it returns False, the
- default rendering method will be used. If it returns None,
- the item will not be rendered in the context of a Table
- construct, that is, can be used to skip columns or constraints
- within op.create_table()::
-
- def my_render_column(type_, col, autogen_context):
- if type_ == "column" and isinstance(col, MySpecialCol):
- return repr(col)
- else:
- return False
-
- context.configure(
- # ...
- render_item = my_render_column
- )
-
- Available values for the type string include: ``"column"``,
- ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``,
- ``"type"``, ``"server_default"``.
-
- .. seealso::
-
- :ref:`autogen_render_types`
-
- :param upgrade_token: When autogenerate completes, the text of the
- candidate upgrade operations will be present in this template
- variable when ``script.py.mako`` is rendered. Defaults to
- ``upgrades``.
- :param downgrade_token: When autogenerate completes, the text of the
- candidate downgrade operations will be present in this
- template variable when ``script.py.mako`` is rendered. Defaults to
- ``downgrades``.
-
- :param alembic_module_prefix: When autogenerate refers to Alembic
- :mod:`alembic.operations` constructs, this prefix will be used
- (i.e. ``op.create_table``) Defaults to "``op.``".
- Can be ``None`` to indicate no prefix.
-
- :param sqlalchemy_module_prefix: When autogenerate refers to
- SQLAlchemy
- :class:`~sqlalchemy.schema.Column` or type classes, this prefix
- will be used
- (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``".
- Can be ``None`` to indicate no prefix.
- Note that when dialect-specific types are rendered, autogenerate
- will render them using the dialect module name, i.e. ``mssql.BIT()``,
- ``postgresql.UUID()``.
-
- :param user_module_prefix: When autogenerate refers to a SQLAlchemy
- type (e.g. :class:`.TypeEngine`) where the module name is not
- under the ``sqlalchemy`` namespace, this prefix will be used
- within autogenerate. If left at its default of
- ``None``, the ``__module__`` attribute of the type is used to
- render the import module. It's a good practice to set this
- and to have all custom types be available from a fixed module space,
- in order to future-proof migration files against reorganizations
- in modules.
-
- .. seealso::
-
- :ref:`autogen_module_prefix`
-
- :param process_revision_directives: a callable function that will
- be passed a structure representing the end result of an autogenerate
- or plain "revision" operation, which can be manipulated to affect
- how the ``alembic revision`` command ultimately outputs new
- revision scripts. The structure of the callable is::
-
- def process_revision_directives(context, revision, directives):
- pass
-
- The ``directives`` parameter is a Python list containing
- a single :class:`.MigrationScript` directive, which represents
- the revision file to be generated. This list as well as its
- contents may be freely modified to produce any set of commands.
- The section :ref:`customizing_revision` shows an example of
- doing this. The ``context`` parameter is the
- :class:`.MigrationContext` in use,
- and ``revision`` is a tuple of revision identifiers representing the
- current revision of the database.
-
- The callable is invoked at all times when the ``--autogenerate``
- option is passed to ``alembic revision``. If ``--autogenerate``
- is not passed, the callable is invoked only if the
- ``revision_environment`` variable is set to True in the Alembic
- configuration, in which case the given ``directives`` collection
- will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps`
- collections for ``.upgrade_ops`` and ``.downgrade_ops``. The
- ``--autogenerate`` option itself can be inferred by inspecting
- ``context.config.cmd_opts.autogenerate``.
-
- The callable function may optionally be an instance of
- a :class:`.Rewriter` object. This is a helper object that
- assists in the production of autogenerate-stream rewriter functions.
-
- .. seealso::
-
- :ref:`customizing_revision`
-
- :ref:`autogen_rewriter`
-
- :paramref:`.command.revision.process_revision_directives`
-
- :param autogenerate_plugins: A list of string names of "plugins" that
- should participate in this autogenerate run. Defaults to the list
- ``["alembic.autogenerate.*"]``, which indicates that Alembic's default
- autogeneration plugins will be used.
-
- See the section :ref:`plugins_autogenerate` for complete background
- on how to use this parameter.
-
- .. versionadded:: 1.18.0 Added a new plugin system for autogenerate
- compare directives.
-
- .. seealso::
-
- :ref:`plugins_autogenerate` - background on enabling/disabling
- autogenerate plugins
-
- :ref:`alembic.plugins.toplevel` - Introduction and documentation
- to the plugin system
-
- Parameters specific to individual backends:
-
- :param mssql_batch_separator: The "batch separator" which will
- be placed between each statement when generating offline SQL Server
- migrations. Defaults to ``GO``. Note this is in addition to the
- customary semicolon ``;`` at the end of each statement; SQL Server
- considers the "batch separator" to denote the end of an
- individual statement execution, and cannot group certain
- dependent operations in one step.
- :param oracle_batch_separator: The "batch separator" which will
- be placed between each statement when generating offline
- Oracle migrations. Defaults to ``/``. Oracle doesn't add a
- semicolon between statements like most other backends.
-
- """
- opts = self.context_opts
- if transactional_ddl is not None:
- opts["transactional_ddl"] = transactional_ddl
- if output_buffer is not None:
- opts["output_buffer"] = output_buffer
- elif self.config.output_buffer is not None:
- opts["output_buffer"] = self.config.output_buffer
- if starting_rev:
- opts["starting_rev"] = starting_rev
- if tag:
- opts["tag"] = tag
- if template_args and "template_args" in opts:
- opts["template_args"].update(template_args)
- opts["transaction_per_migration"] = transaction_per_migration
- opts["target_metadata"] = target_metadata
- opts["include_name"] = include_name
- opts["include_object"] = include_object
- opts["include_schemas"] = include_schemas
- opts["render_as_batch"] = render_as_batch
- opts["upgrade_token"] = upgrade_token
- opts["downgrade_token"] = downgrade_token
- opts["sqlalchemy_module_prefix"] = sqlalchemy_module_prefix
- opts["alembic_module_prefix"] = alembic_module_prefix
- opts["user_module_prefix"] = user_module_prefix
- opts["literal_binds"] = literal_binds
- opts["process_revision_directives"] = process_revision_directives
- opts["on_version_apply"] = util.to_tuple(on_version_apply, default=())
-
- if autogenerate_plugins is not None:
- opts["autogenerate_plugins"] = autogenerate_plugins
-
- if render_item is not None:
- opts["render_item"] = render_item
- opts["compare_type"] = compare_type
- if compare_server_default is not None:
- opts["compare_server_default"] = compare_server_default
- opts["script"] = self.script
-
- opts.update(kw)
-
- self._migration_context = MigrationContext.configure(
- connection=connection,
- url=url,
- dialect_name=dialect_name,
- environment_context=self,
- dialect_opts=dialect_opts,
- opts=opts,
- )
-
- def run_migrations(self, **kw: Any) -> None:
- """Run migrations as determined by the current command line
- configuration
- as well as versioning information present (or not) in the current
- database connection (if one is present).
-
- The function accepts optional ``**kw`` arguments. If these are
- passed, they are sent directly to the ``upgrade()`` and
- ``downgrade()``
- functions within each target revision file. By modifying the
- ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()``
- functions accept arguments, parameters can be passed here so that
- contextual information, usually information to identify a particular
- database in use, can be passed from a custom ``env.py`` script
- to the migration functions.
-
- This function requires that a :class:`.MigrationContext` has
- first been made available via :meth:`.configure`.
-
- """
- assert self._migration_context is not None
- with Operations.context(self._migration_context):
- self.get_context().run_migrations(**kw)
-
- def execute(
- self,
- sql: Union[Executable, str],
- execution_options: Optional[Dict[str, Any]] = None,
- ) -> None:
- """Execute the given SQL using the current change context.
-
- The behavior of :meth:`.execute` is the same
- as that of :meth:`.Operations.execute`. Please see that
- function's documentation for full detail including
- caveats and limitations.
-
- This function requires that a :class:`.MigrationContext` has
- first been made available via :meth:`.configure`.
-
- """
- self.get_context().execute(sql, execution_options=execution_options)
-
- def static_output(self, text: str) -> None:
- """Emit text directly to the "offline" SQL stream.
-
- Typically this is for emitting comments that
- start with --. The statement is not treated
- as a SQL execution, no ; or batch separator
- is added, etc.
-
- """
- self.get_context().impl.static_output(text)
-
- def begin_transaction(
- self,
- ) -> Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]:
- """Return a context manager that will
- enclose an operation within a "transaction",
- as defined by the environment's offline
- and transactional DDL settings.
-
- e.g.::
-
- with context.begin_transaction():
- context.run_migrations()
-
- :meth:`.begin_transaction` is intended to
- "do the right thing" regardless of
- calling context:
-
- * If :meth:`.is_transactional_ddl` is ``False``,
- returns a "do nothing" context manager
- which otherwise produces no transactional
- state or directives.
- * If :meth:`.is_offline_mode` is ``True``,
- returns a context manager that will
- invoke the :meth:`.DefaultImpl.emit_begin`
- and :meth:`.DefaultImpl.emit_commit`
- methods, which will produce the string
- directives ``BEGIN`` and ``COMMIT`` on
- the output stream, as rendered by the
- target backend (e.g. SQL Server would
- emit ``BEGIN TRANSACTION``).
- * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin`
- on the current online connection, which
- returns a :class:`sqlalchemy.engine.Transaction`
- object. This object demarcates a real
- transaction and is itself a context manager,
- which will roll back if an exception
- is raised.
-
- Note that a custom ``env.py`` script which
- has more specific transactional needs can of course
- manipulate the :class:`~sqlalchemy.engine.Connection`
- directly to produce transactional state in "online"
- mode.
-
- """
-
- return self.get_context().begin_transaction()
-
- def get_context(self) -> MigrationContext:
- """Return the current :class:`.MigrationContext` object.
-
- If :meth:`.EnvironmentContext.configure` has not been
- called yet, raises an exception.
-
- """
-
- if self._migration_context is None:
- raise Exception("No context has been configured yet.")
- return self._migration_context
-
- def get_bind(self) -> Connection:
- """Return the current 'bind'.
-
- In "online" mode, this is the
- :class:`sqlalchemy.engine.Connection` currently being used
- to emit SQL to the database.
-
- This function requires that a :class:`.MigrationContext`
- has first been made available via :meth:`.configure`.
-
- """
- return self.get_context().bind # type: ignore[return-value]
-
- def get_impl(self) -> DefaultImpl:
- return self.get_context().impl
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/migration.py b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/migration.py
deleted file mode 100644
index 3fccf22..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/migration.py
+++ /dev/null
@@ -1,1346 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-from contextlib import contextmanager
-from contextlib import nullcontext
-import logging
-import sys
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from sqlalchemy import literal_column
-from sqlalchemy import select
-from sqlalchemy.engine import Engine
-from sqlalchemy.engine import url as sqla_url
-from sqlalchemy.engine.strategies import MockEngineStrategy
-from typing_extensions import ContextManager
-
-from .. import ddl
-from .. import util
-from ..util import sqla_compat
-from ..util.compat import EncodedIO
-
-if TYPE_CHECKING:
- from sqlalchemy.engine import Dialect
- from sqlalchemy.engine import URL
- from sqlalchemy.engine.base import Connection
- from sqlalchemy.engine.base import Transaction
- from sqlalchemy.engine.mock import MockConnection
- from sqlalchemy.sql import Executable
-
- from .environment import EnvironmentContext
- from ..config import Config
- from ..script.base import Script
- from ..script.base import ScriptDirectory
- from ..script.revision import _RevisionOrBase
- from ..script.revision import Revision
- from ..script.revision import RevisionMap
-
-log = logging.getLogger(__name__)
-
-
-class _ProxyTransaction:
- def __init__(self, migration_context: MigrationContext) -> None:
- self.migration_context = migration_context
-
- @property
- def _proxied_transaction(self) -> Optional[Transaction]:
- return self.migration_context._transaction
-
- def rollback(self) -> None:
- t = self._proxied_transaction
- assert t is not None
- t.rollback()
- self.migration_context._transaction = None
-
- def commit(self) -> None:
- t = self._proxied_transaction
- assert t is not None
- t.commit()
- self.migration_context._transaction = None
-
- def __enter__(self) -> _ProxyTransaction:
- return self
-
- def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
- if self._proxied_transaction is not None:
- self._proxied_transaction.__exit__(type_, value, traceback)
- self.migration_context._transaction = None
-
-
-class MigrationContext:
- """Represent the database state made available to a migration
- script.
-
- :class:`.MigrationContext` is the front end to an actual
- database connection, or alternatively a string output
- stream given a particular database dialect,
- from an Alembic perspective.
-
- When inside the ``env.py`` script, the :class:`.MigrationContext`
- is available via the
- :meth:`.EnvironmentContext.get_context` method,
- which is available at ``alembic.context``::
-
- # from within env.py script
- from alembic import context
-
- migration_context = context.get_context()
-
- For usage outside of an ``env.py`` script, such as for
- utility routines that want to check the current version
- in the database, the :meth:`.MigrationContext.configure`
- method to create new :class:`.MigrationContext` objects.
- For example, to get at the current revision in the
- database using :meth:`.MigrationContext.get_current_revision`::
-
- # in any application, outside of an env.py script
- from alembic.migration import MigrationContext
- from sqlalchemy import create_engine
-
- engine = create_engine("postgresql://mydatabase")
- conn = engine.connect()
-
- context = MigrationContext.configure(conn)
- current_rev = context.get_current_revision()
-
- The above context can also be used to produce
- Alembic migration operations with an :class:`.Operations`
- instance::
-
- # in any application, outside of the normal Alembic environment
- from alembic.operations import Operations
-
- op = Operations(context)
- op.alter_column("mytable", "somecolumn", nullable=True)
-
- """
-
- def __init__(
- self,
- dialect: Dialect,
- connection: Optional[Connection],
- opts: Dict[str, Any],
- environment_context: Optional[EnvironmentContext] = None,
- ) -> None:
- self.environment_context = environment_context
- self.opts = opts
- self.dialect = dialect
- self.script: Optional[ScriptDirectory] = opts.get("script")
- as_sql: bool = opts.get("as_sql", False)
- transactional_ddl = opts.get("transactional_ddl")
- self._transaction_per_migration = opts.get(
- "transaction_per_migration", False
- )
- self.on_version_apply_callbacks = opts.get("on_version_apply", ())
- self._transaction: Optional[Transaction] = None
-
- if as_sql:
- self.connection = cast(
- Optional["Connection"], self._stdout_connection(connection)
- )
- assert self.connection is not None
- self._in_external_transaction = False
- else:
- self.connection = connection
- self._in_external_transaction = (
- sqla_compat._get_connection_in_transaction(connection)
- )
-
- self._migrations_fn: Optional[
- Callable[..., Iterable[RevisionStep]]
- ] = opts.get("fn")
- self.as_sql = as_sql
-
- self.purge = opts.get("purge", False)
-
- if "output_encoding" in opts:
- self.output_buffer = EncodedIO(
- opts.get("output_buffer")
- or sys.stdout, # type:ignore[arg-type]
- opts["output_encoding"],
- )
- else:
- self.output_buffer = opts.get(
- "output_buffer", sys.stdout
- ) # type:ignore[assignment] # noqa: E501
-
- self.transactional_ddl = transactional_ddl
-
- self._user_compare_type = opts.get("compare_type", True)
- self._user_compare_server_default = opts.get(
- "compare_server_default", False
- )
- self.version_table = version_table = opts.get(
- "version_table", "alembic_version"
- )
- self.version_table_schema = version_table_schema = opts.get(
- "version_table_schema", None
- )
-
- self._start_from_rev: Optional[str] = opts.get("starting_rev")
- self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
- dialect,
- self.connection,
- self.as_sql,
- transactional_ddl,
- self.output_buffer,
- opts,
- )
-
- self._version = self.impl.version_table_impl(
- version_table=version_table,
- version_table_schema=version_table_schema,
- version_table_pk=opts.get("version_table_pk", True),
- )
-
- log.info("Context impl %s.", self.impl.__class__.__name__)
- if self.as_sql:
- log.info("Generating static SQL")
- log.info(
- "Will assume %s DDL.",
- (
- "transactional"
- if self.impl.transactional_ddl
- else "non-transactional"
- ),
- )
-
- @classmethod
- def configure(
- cls,
- connection: Optional[Connection] = None,
- url: Optional[Union[str, URL]] = None,
- dialect_name: Optional[str] = None,
- dialect: Optional[Dialect] = None,
- environment_context: Optional[EnvironmentContext] = None,
- dialect_opts: Optional[Dict[str, str]] = None,
- opts: Optional[Any] = None,
- ) -> MigrationContext:
- """Create a new :class:`.MigrationContext`.
-
- This is a factory method usually called
- by :meth:`.EnvironmentContext.configure`.
-
- :param connection: a :class:`~sqlalchemy.engine.Connection`
- to use for SQL execution in "online" mode. When present,
- is also used to determine the type of dialect in use.
- :param url: a string database url, or a
- :class:`sqlalchemy.engine.url.URL` object.
- The type of dialect to be used will be derived from this if
- ``connection`` is not passed.
- :param dialect_name: string name of a dialect, such as
- "postgresql", "mssql", etc. The type of dialect to be used will be
- derived from this if ``connection`` and ``url`` are not passed.
- :param opts: dictionary of options. Most other options
- accepted by :meth:`.EnvironmentContext.configure` are passed via
- this dictionary.
-
- """
- if opts is None:
- opts = {}
- if dialect_opts is None:
- dialect_opts = {}
-
- if connection:
- if isinstance(connection, Engine):
- raise util.CommandError(
- "'connection' argument to configure() is expected "
- "to be a sqlalchemy.engine.Connection instance, "
- "got %r" % connection,
- )
-
- dialect = connection.dialect
- elif url:
- url_obj = sqla_url.make_url(url)
- dialect = url_obj.get_dialect()(**dialect_opts)
- elif dialect_name:
- url_obj = sqla_url.make_url("%s://" % dialect_name)
- dialect = url_obj.get_dialect()(**dialect_opts)
- elif not dialect:
- raise Exception("Connection, url, or dialect_name is required.")
- assert dialect is not None
- return MigrationContext(dialect, connection, opts, environment_context)
-
- @contextmanager
- def autocommit_block(self) -> Iterator[None]:
- """Enter an "autocommit" block, for databases that support AUTOCOMMIT
- isolation levels.
-
- This special directive is intended to support the occasional database
- DDL or system operation that specifically has to be run outside of
- any kind of transaction block. The PostgreSQL database platform
- is the most common target for this style of operation, as many
- of its DDL operations must be run outside of transaction blocks, even
- though the database overall supports transactional DDL.
-
- The method is used as a context manager within a migration script, by
- calling on :meth:`.Operations.get_context` to retrieve the
- :class:`.MigrationContext`, then invoking
- :meth:`.MigrationContext.autocommit_block` using the ``with:``
- statement::
-
- def upgrade():
- with op.get_context().autocommit_block():
- op.execute("ALTER TYPE mood ADD VALUE 'soso'")
-
- Above, a PostgreSQL "ALTER TYPE..ADD VALUE" directive is emitted,
- which must be run outside of a transaction block at the database level.
- The :meth:`.MigrationContext.autocommit_block` method makes use of the
- SQLAlchemy ``AUTOCOMMIT`` isolation level setting, which against the
- psycogp2 DBAPI corresponds to the ``connection.autocommit`` setting,
- to ensure that the database driver is not inside of a DBAPI level
- transaction block.
-
- .. warning::
-
- As is necessary, **the database transaction preceding the block is
- unconditionally committed**. This means that the run of migrations
- preceding the operation will be committed, before the overall
- migration operation is complete.
-
- It is recommended that when an application includes migrations with
- "autocommit" blocks, that
- :paramref:`.EnvironmentContext.transaction_per_migration` be used
- so that the calling environment is tuned to expect short per-file
- migrations whether or not one of them has an autocommit block.
-
-
- """
- _in_connection_transaction = self._in_connection_transaction()
-
- if self.impl.transactional_ddl and self.as_sql:
- self.impl.emit_commit()
-
- elif _in_connection_transaction:
- assert self._transaction is not None
-
- self._transaction.commit()
- self._transaction = None
-
- if not self.as_sql:
- assert self.connection is not None
- current_level = self.connection.get_isolation_level()
- base_connection = self.connection
-
- # in 1.3 and 1.4 non-future mode, the connection gets switched
- # out. we can use the base connection with the new mode
- # except that it will not know it's in "autocommit" and will
- # emit deprecation warnings when an autocommit action takes
- # place.
- self.connection = self.impl.connection = (
- base_connection.execution_options(isolation_level="AUTOCOMMIT")
- )
-
- # sqlalchemy future mode will "autobegin" in any case, so take
- # control of that "transaction" here
- fake_trans: Optional[Transaction] = self.connection.begin()
- else:
- fake_trans = None
- try:
- yield
- finally:
- if not self.as_sql:
- assert self.connection is not None
- if fake_trans is not None:
- fake_trans.commit()
- self.connection.execution_options(
- isolation_level=current_level
- )
- self.connection = self.impl.connection = base_connection
-
- if self.impl.transactional_ddl and self.as_sql:
- self.impl.emit_begin()
-
- elif _in_connection_transaction:
- assert self.connection is not None
- self._transaction = self.connection.begin()
-
- def begin_transaction(
- self, _per_migration: bool = False
- ) -> Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]:
- """Begin a logical transaction for migration operations.
-
- This method is used within an ``env.py`` script to demarcate where
- the outer "transaction" for a series of migrations begins. Example::
-
- def run_migrations_online():
- connectable = create_engine(...)
-
- with connectable.connect() as connection:
- context.configure(
- connection=connection, target_metadata=target_metadata
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
- Above, :meth:`.MigrationContext.begin_transaction` is used to demarcate
- where the outer logical transaction occurs around the
- :meth:`.MigrationContext.run_migrations` operation.
-
- A "Logical" transaction means that the operation may or may not
- correspond to a real database transaction. If the target database
- supports transactional DDL (or
- :paramref:`.EnvironmentContext.configure.transactional_ddl` is true),
- the :paramref:`.EnvironmentContext.configure.transaction_per_migration`
- flag is not set, and the migration is against a real database
- connection (as opposed to using "offline" ``--sql`` mode), a real
- transaction will be started. If ``--sql`` mode is in effect, the
- operation would instead correspond to a string such as "BEGIN" being
- emitted to the string output.
-
- The returned object is a Python context manager that should only be
- used in the context of a ``with:`` statement as indicated above.
- The object has no other guaranteed API features present.
-
- .. seealso::
-
- :meth:`.MigrationContext.autocommit_block`
-
- """
-
- if self._in_external_transaction:
- return nullcontext()
-
- if self.impl.transactional_ddl:
- transaction_now = _per_migration == self._transaction_per_migration
- else:
- transaction_now = _per_migration is True
-
- if not transaction_now:
- return nullcontext()
-
- elif not self.impl.transactional_ddl:
- assert _per_migration
-
- if self.as_sql:
- return nullcontext()
- else:
- # track our own notion of a "transaction block", which must be
- # committed when complete. Don't rely upon whether or not the
- # SQLAlchemy connection reports as "in transaction"; this
- # because SQLAlchemy future connection features autobegin
- # behavior, so it may already be in a transaction from our
- # emitting of queries like "has_version_table", etc. While we
- # could track these operations as well, that leaves open the
- # possibility of new operations or other things happening in
- # the user environment that still may be triggering
- # "autobegin".
-
- in_transaction = self._transaction is not None
-
- if in_transaction:
- return nullcontext()
- else:
- assert self.connection is not None
- self._transaction = (
- sqla_compat._safe_begin_connection_transaction(
- self.connection
- )
- )
- return _ProxyTransaction(self)
- elif self.as_sql:
-
- @contextmanager
- def begin_commit():
- self.impl.emit_begin()
- yield
- self.impl.emit_commit()
-
- return begin_commit()
- else:
- assert self.connection is not None
- self._transaction = sqla_compat._safe_begin_connection_transaction(
- self.connection
- )
- return _ProxyTransaction(self)
-
- def get_current_revision(self) -> Optional[str]:
- """Return the current revision, usually that which is present
- in the ``alembic_version`` table in the database.
-
- This method intends to be used only for a migration stream that
- does not contain unmerged branches in the target database;
- if there are multiple branches present, an exception is raised.
- The :meth:`.MigrationContext.get_current_heads` should be preferred
- over this method going forward in order to be compatible with
- branch migration support.
-
- If this :class:`.MigrationContext` was configured in "offline"
- mode, that is with ``as_sql=True``, the ``starting_rev``
- parameter is returned instead, if any.
-
- """
- heads = self.get_current_heads()
- if len(heads) == 0:
- return None
- elif len(heads) > 1:
- raise util.CommandError(
- "Version table '%s' has more than one head present; "
- "please use get_current_heads()" % self.version_table
- )
- else:
- return heads[0]
-
- def get_current_heads(self) -> Tuple[str, ...]:
- """Return a tuple of the current 'head versions' that are represented
- in the target database.
-
- For a migration stream without branches, this will be a single
- value, synonymous with that of
- :meth:`.MigrationContext.get_current_revision`. However when multiple
- unmerged branches exist within the target database, the returned tuple
- will contain a value for each head.
-
- If this :class:`.MigrationContext` was configured in "offline"
- mode, that is with ``as_sql=True``, the ``starting_rev``
- parameter is returned in a one-length tuple.
-
- If no version table is present, or if there are no revisions
- present, an empty tuple is returned.
-
- """
- if self.as_sql:
- start_from_rev: Any = self._start_from_rev
- if start_from_rev == "base":
- start_from_rev = None
- elif start_from_rev is not None and self.script:
- start_from_rev = [
- self.script.get_revision(sfr).revision
- for sfr in util.to_list(start_from_rev)
- if sfr not in (None, "base")
- ]
- return util.to_tuple(start_from_rev, default=())
- else:
- if self._start_from_rev:
- raise util.CommandError(
- "Can't specify current_rev to context "
- "when using a database connection"
- )
- if not self._has_version_table():
- return ()
- assert self.connection is not None
- return tuple(
- row[0]
- for row in self.connection.execute(
- select(self._version.c.version_num)
- )
- )
-
- def _ensure_version_table(self, purge: bool = False) -> None:
- with sqla_compat._ensure_scope_for_ddl(self.connection):
- assert self.connection is not None
- self._version.create(self.connection, checkfirst=True)
- if purge:
- assert self.connection is not None
- self.connection.execute(self._version.delete())
-
- def _has_version_table(self) -> bool:
- assert self.connection is not None
- return sqla_compat._connectable_has_table(
- self.connection, self.version_table, self.version_table_schema
- )
-
- def stamp(self, script_directory: ScriptDirectory, revision: str) -> None:
- """Stamp the version table with a specific revision.
-
- This method calculates those branches to which the given revision
- can apply, and updates those branches as though they were migrated
- towards that revision (either up or down). If no current branches
- include the revision, it is added as a new branch head.
-
- """
- heads = self.get_current_heads()
- if not self.as_sql and not heads:
- self._ensure_version_table()
- head_maintainer = HeadMaintainer(self, heads)
- for step in script_directory._stamp_revs(revision, heads):
- head_maintainer.update_to_step(step)
-
- def run_migrations(self, **kw: Any) -> None:
- r"""Run the migration scripts established for this
- :class:`.MigrationContext`, if any.
-
- The commands in :mod:`alembic.command` will set up a function
- that is ultimately passed to the :class:`.MigrationContext`
- as the ``fn`` argument. This function represents the "work"
- that will be done when :meth:`.MigrationContext.run_migrations`
- is called, typically from within the ``env.py`` script of the
- migration environment. The "work function" then provides an iterable
- of version callables and other version information which
- in the case of the ``upgrade`` or ``downgrade`` commands are the
- list of version scripts to invoke. Other commands yield nothing,
- in the case that a command wants to run some other operation
- against the database such as the ``current`` or ``stamp`` commands.
-
- :param \**kw: keyword arguments here will be passed to each
- migration callable, that is the ``upgrade()`` or ``downgrade()``
- method within revision scripts.
-
- """
- self.impl.start_migrations()
-
- heads: Tuple[str, ...]
- if self.purge:
- if self.as_sql:
- raise util.CommandError("Can't use --purge with --sql mode")
- self._ensure_version_table(purge=True)
- heads = ()
- else:
- heads = self.get_current_heads()
-
- dont_mutate = self.opts.get("dont_mutate", False)
-
- if not self.as_sql and not heads and not dont_mutate:
- self._ensure_version_table()
-
- head_maintainer = HeadMaintainer(self, heads)
-
- assert self._migrations_fn is not None
- for step in self._migrations_fn(heads, self):
- with self.begin_transaction(_per_migration=True):
- if self.as_sql and not head_maintainer.heads:
- # for offline mode, include a CREATE TABLE from
- # the base
- assert self.connection is not None
- self._version.create(self.connection)
- log.info("Running %s", step)
- if self.as_sql:
- self.impl.static_output(
- "-- Running %s" % (step.short_log,)
- )
- step.migration_fn(**kw)
-
- # previously, we wouldn't stamp per migration
- # if we were in a transaction, however given the more
- # complex model that involves any number of inserts
- # and row-targeted updates and deletes, it's simpler for now
- # just to run the operations on every version
- head_maintainer.update_to_step(step)
- for callback in self.on_version_apply_callbacks:
- callback(
- ctx=self,
- step=step.info,
- heads=set(head_maintainer.heads),
- run_args=kw,
- )
-
- if self.as_sql and not head_maintainer.heads:
- assert self.connection is not None
- self._version.drop(self.connection)
-
- def _in_connection_transaction(self) -> bool:
- try:
- meth = self.connection.in_transaction # type:ignore[union-attr]
- except AttributeError:
- return False
- else:
- return meth()
-
- def execute(
- self,
- sql: Union[Executable, str],
- execution_options: Optional[Dict[str, Any]] = None,
- ) -> None:
- """Execute a SQL construct or string statement.
-
- The underlying execution mechanics are used, that is
- if this is "offline mode" the SQL is written to the
- output buffer, otherwise the SQL is emitted on
- the current SQLAlchemy connection.
-
- """
- self.impl._exec(sql, execution_options)
-
- def _stdout_connection(
- self, connection: Optional[Connection]
- ) -> MockConnection:
- def dump(construct, *multiparams, **params):
- self.impl._exec(construct)
-
- return MockEngineStrategy.MockConnection(self.dialect, dump)
-
- @property
- def bind(self) -> Optional[Connection]:
- """Return the current "bind".
-
- In online mode, this is an instance of
- :class:`sqlalchemy.engine.Connection`, and is suitable
- for ad-hoc execution of any kind of usage described
- in SQLAlchemy Core documentation as well as
- for usage with the :meth:`sqlalchemy.schema.Table.create`
- and :meth:`sqlalchemy.schema.MetaData.create_all` methods
- of :class:`~sqlalchemy.schema.Table`,
- :class:`~sqlalchemy.schema.MetaData`.
-
- Note that when "standard output" mode is enabled,
- this bind will be a "mock" connection handler that cannot
- return results and is only appropriate for a very limited
- subset of commands.
-
- """
- return self.connection
-
- @property
- def config(self) -> Optional[Config]:
- """Return the :class:`.Config` used by the current environment,
- if any."""
-
- if self.environment_context:
- return self.environment_context.config
- else:
- return None
-
-
-class HeadMaintainer:
- def __init__(self, context: MigrationContext, heads: Any) -> None:
- self.context = context
- self.heads = set(heads)
-
- def _insert_version(self, version: str) -> None:
- assert version not in self.heads
- self.heads.add(version)
-
- self.context.impl._exec(
- self.context._version.insert().values(
- version_num=literal_column("'%s'" % version)
- )
- )
-
- def _delete_version(self, version: str) -> None:
- self.heads.remove(version)
-
- ret = self.context.impl._exec(
- self.context._version.delete().where(
- self.context._version.c.version_num
- == literal_column("'%s'" % version)
- )
- )
-
- if (
- not self.context.as_sql
- and self.context.dialect.supports_sane_rowcount
- and ret is not None
- and ret.rowcount != 1
- ):
- raise util.CommandError(
- "Online migration expected to match one "
- "row when deleting '%s' in '%s'; "
- "%d found"
- % (version, self.context.version_table, ret.rowcount)
- )
-
- def _update_version(self, from_: str, to_: str) -> None:
- assert to_ not in self.heads
- self.heads.remove(from_)
- self.heads.add(to_)
-
- ret = self.context.impl._exec(
- self.context._version.update()
- .values(version_num=literal_column("'%s'" % to_))
- .where(
- self.context._version.c.version_num
- == literal_column("'%s'" % from_)
- )
- )
-
- if (
- not self.context.as_sql
- and self.context.dialect.supports_sane_rowcount
- and ret is not None
- and ret.rowcount != 1
- ):
- raise util.CommandError(
- "Online migration expected to match one "
- "row when updating '%s' to '%s' in '%s'; "
- "%d found"
- % (from_, to_, self.context.version_table, ret.rowcount)
- )
-
- def update_to_step(self, step: Union[RevisionStep, StampStep]) -> None:
- if step.should_delete_branch(self.heads):
- vers = step.delete_version_num
- log.debug("branch delete %s", vers)
- self._delete_version(vers)
- elif step.should_create_branch(self.heads):
- vers = step.insert_version_num
- log.debug("new branch insert %s", vers)
- self._insert_version(vers)
- elif step.should_merge_branches(self.heads):
- # delete revs, update from rev, update to rev
- (
- delete_revs,
- update_from_rev,
- update_to_rev,
- ) = step.merge_branch_idents(self.heads)
- log.debug(
- "merge, delete %s, update %s to %s",
- delete_revs,
- update_from_rev,
- update_to_rev,
- )
- for delrev in delete_revs:
- self._delete_version(delrev)
- self._update_version(update_from_rev, update_to_rev)
- elif step.should_unmerge_branches(self.heads):
- (
- update_from_rev,
- update_to_rev,
- insert_revs,
- ) = step.unmerge_branch_idents(self.heads)
- log.debug(
- "unmerge, insert %s, update %s to %s",
- insert_revs,
- update_from_rev,
- update_to_rev,
- )
- for insrev in insert_revs:
- self._insert_version(insrev)
- self._update_version(update_from_rev, update_to_rev)
- else:
- from_, to_ = step.update_version_num(self.heads)
- log.debug("update %s to %s", from_, to_)
- self._update_version(from_, to_)
-
-
-class MigrationInfo:
- """Exposes information about a migration step to a callback listener.
-
- The :class:`.MigrationInfo` object is available exclusively for the
- benefit of the :paramref:`.EnvironmentContext.on_version_apply`
- callback hook.
-
- """
-
- is_upgrade: bool
- """True/False: indicates whether this operation ascends or descends the
- version tree."""
-
- is_stamp: bool
- """True/False: indicates whether this operation is a stamp (i.e. whether
- it results in any actual database operations)."""
-
- up_revision_id: Optional[str]
- """Version string corresponding to :attr:`.Revision.revision`.
-
- In the case of a stamp operation, it is advised to use the
- :attr:`.MigrationInfo.up_revision_ids` tuple as a stamp operation can
- make a single movement from one or more branches down to a single
- branchpoint, in which case there will be multiple "up" revisions.
-
- .. seealso::
-
- :attr:`.MigrationInfo.up_revision_ids`
-
- """
-
- up_revision_ids: Tuple[str, ...]
- """Tuple of version strings corresponding to :attr:`.Revision.revision`.
-
- In the majority of cases, this tuple will be a single value, synonymous
- with the scalar value of :attr:`.MigrationInfo.up_revision_id`.
- It can be multiple revision identifiers only in the case of an
- ``alembic stamp`` operation which is moving downwards from multiple
- branches down to their common branch point.
-
- """
-
- down_revision_ids: Tuple[str, ...]
- """Tuple of strings representing the base revisions of this migration step.
-
- If empty, this represents a root revision; otherwise, the first item
- corresponds to :attr:`.Revision.down_revision`, and the rest are inferred
- from dependencies.
- """
-
- revision_map: RevisionMap
- """The revision map inside of which this operation occurs."""
-
- def __init__(
- self,
- revision_map: RevisionMap,
- is_upgrade: bool,
- is_stamp: bool,
- up_revisions: Union[str, Tuple[str, ...]],
- down_revisions: Union[str, Tuple[str, ...]],
- ) -> None:
- self.revision_map = revision_map
- self.is_upgrade = is_upgrade
- self.is_stamp = is_stamp
- self.up_revision_ids = util.to_tuple(up_revisions, default=())
- if self.up_revision_ids:
- self.up_revision_id = self.up_revision_ids[0]
- else:
- # this should never be the case with
- # "upgrade", "downgrade", or "stamp" as we are always
- # measuring movement in terms of at least one upgrade version
- self.up_revision_id = None
- self.down_revision_ids = util.to_tuple(down_revisions, default=())
-
- @property
- def is_migration(self) -> bool:
- """True/False: indicates whether this operation is a migration.
-
- At present this is true if and only the migration is not a stamp.
- If other operation types are added in the future, both this attribute
- and :attr:`~.MigrationInfo.is_stamp` will be false.
- """
- return not self.is_stamp
-
- @property
- def source_revision_ids(self) -> Tuple[str, ...]:
- """Active revisions before this migration step is applied."""
- return (
- self.down_revision_ids if self.is_upgrade else self.up_revision_ids
- )
-
- @property
- def destination_revision_ids(self) -> Tuple[str, ...]:
- """Active revisions after this migration step is applied."""
- return (
- self.up_revision_ids if self.is_upgrade else self.down_revision_ids
- )
-
- @property
- def up_revision(self) -> Optional[Revision]:
- """Get :attr:`~.MigrationInfo.up_revision_id` as
- a :class:`.Revision`.
-
- """
- return self.revision_map.get_revision(self.up_revision_id)
-
- @property
- def up_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
- """Get :attr:`~.MigrationInfo.up_revision_ids` as a
- :class:`.Revision`."""
- return self.revision_map.get_revisions(self.up_revision_ids)
-
- @property
- def down_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
- """Get :attr:`~.MigrationInfo.down_revision_ids` as a tuple of
- :class:`Revisions <.Revision>`."""
- return self.revision_map.get_revisions(self.down_revision_ids)
-
- @property
- def source_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
- """Get :attr:`~MigrationInfo.source_revision_ids` as a tuple of
- :class:`Revisions <.Revision>`."""
- return self.revision_map.get_revisions(self.source_revision_ids)
-
- @property
- def destination_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]:
- """Get :attr:`~MigrationInfo.destination_revision_ids` as a tuple of
- :class:`Revisions <.Revision>`."""
- return self.revision_map.get_revisions(self.destination_revision_ids)
-
-
-class MigrationStep:
- from_revisions_no_deps: Tuple[str, ...]
- to_revisions_no_deps: Tuple[str, ...]
- is_upgrade: bool
- migration_fn: Any
-
- if TYPE_CHECKING:
-
- @property
- def doc(self) -> Optional[str]: ...
-
- @property
- def name(self) -> str:
- return self.migration_fn.__name__
-
- @classmethod
- def upgrade_from_script(
- cls, revision_map: RevisionMap, script: Script
- ) -> RevisionStep:
- return RevisionStep(revision_map, script, True)
-
- @classmethod
- def downgrade_from_script(
- cls, revision_map: RevisionMap, script: Script
- ) -> RevisionStep:
- return RevisionStep(revision_map, script, False)
-
- @property
- def is_downgrade(self) -> bool:
- return not self.is_upgrade
-
- @property
- def short_log(self) -> str:
- return "%s %s -> %s" % (
- self.name,
- util.format_as_comma(self.from_revisions_no_deps),
- util.format_as_comma(self.to_revisions_no_deps),
- )
-
- def __str__(self):
- if self.doc:
- return "%s %s -> %s, %s" % (
- self.name,
- util.format_as_comma(self.from_revisions_no_deps),
- util.format_as_comma(self.to_revisions_no_deps),
- self.doc,
- )
- else:
- return self.short_log
-
-
-class RevisionStep(MigrationStep):
- def __init__(
- self, revision_map: RevisionMap, revision: Script, is_upgrade: bool
- ) -> None:
- self.revision_map = revision_map
- self.revision = revision
- self.is_upgrade = is_upgrade
- if is_upgrade:
- self.migration_fn = revision.module.upgrade
- else:
- self.migration_fn = revision.module.downgrade
-
- def __repr__(self):
- return "RevisionStep(%r, is_upgrade=%r)" % (
- self.revision.revision,
- self.is_upgrade,
- )
-
- def __eq__(self, other: object) -> bool:
- return (
- isinstance(other, RevisionStep)
- and other.revision == self.revision
- and self.is_upgrade == other.is_upgrade
- )
-
- @property
- def doc(self) -> Optional[str]:
- return self.revision.doc
-
- @property
- def from_revisions(self) -> Tuple[str, ...]:
- if self.is_upgrade:
- return self.revision._normalized_down_revisions
- else:
- return (self.revision.revision,)
-
- @property
- def from_revisions_no_deps( # type:ignore[override]
- self,
- ) -> Tuple[str, ...]:
- if self.is_upgrade:
- return self.revision._versioned_down_revisions
- else:
- return (self.revision.revision,)
-
- @property
- def to_revisions(self) -> Tuple[str, ...]:
- if self.is_upgrade:
- return (self.revision.revision,)
- else:
- return self.revision._normalized_down_revisions
-
- @property
- def to_revisions_no_deps( # type:ignore[override]
- self,
- ) -> Tuple[str, ...]:
- if self.is_upgrade:
- return (self.revision.revision,)
- else:
- return self.revision._versioned_down_revisions
-
- @property
- def _has_scalar_down_revision(self) -> bool:
- return len(self.revision._normalized_down_revisions) == 1
-
- def should_delete_branch(self, heads: Set[str]) -> bool:
- """A delete is when we are a. in a downgrade and b.
- we are going to the "base" or we are going to a version that
- is implied as a dependency on another version that is remaining.
-
- """
- if not self.is_downgrade:
- return False
-
- if self.revision.revision not in heads:
- return False
-
- downrevs = self.revision._normalized_down_revisions
-
- if not downrevs:
- # is a base
- return True
- else:
- # determine what the ultimate "to_revisions" for an
- # unmerge would be. If there are none, then we're a delete.
- to_revisions = self._unmerge_to_revisions(heads)
- return not to_revisions
-
- def merge_branch_idents(
- self, heads: Set[str]
- ) -> Tuple[List[str], str, str]:
- other_heads = set(heads).difference(self.from_revisions)
-
- if other_heads:
- ancestors = {
- r.revision
- for r in self.revision_map._get_ancestor_nodes(
- self.revision_map.get_revisions(other_heads), check=False
- )
- }
- from_revisions = list(
- set(self.from_revisions).difference(ancestors)
- )
- else:
- from_revisions = list(self.from_revisions)
-
- return (
- # delete revs, update from rev, update to rev
- list(from_revisions[0:-1]),
- from_revisions[-1],
- self.to_revisions[0],
- )
-
- def _unmerge_to_revisions(self, heads: Set[str]) -> Tuple[str, ...]:
- other_heads = set(heads).difference([self.revision.revision])
- if other_heads:
- ancestors = {
- r.revision
- for r in self.revision_map._get_ancestor_nodes(
- self.revision_map.get_revisions(other_heads), check=False
- )
- }
- return tuple(set(self.to_revisions).difference(ancestors))
- else:
- # for each revision we plan to return, compute its ancestors
- # (excluding self), and remove those from the final output since
- # they are already accounted for.
- ancestors = {
- r.revision
- for to_revision in self.to_revisions
- for r in self.revision_map._get_ancestor_nodes(
- self.revision_map.get_revisions(to_revision), check=False
- )
- if r.revision != to_revision
- }
- return tuple(set(self.to_revisions).difference(ancestors))
-
- def unmerge_branch_idents(
- self, heads: Set[str]
- ) -> Tuple[str, str, Tuple[str, ...]]:
- to_revisions = self._unmerge_to_revisions(heads)
-
- return (
- # update from rev, update to rev, insert revs
- self.from_revisions[0],
- to_revisions[-1],
- to_revisions[0:-1],
- )
-
- def should_create_branch(self, heads: Set[str]) -> bool:
- if not self.is_upgrade:
- return False
-
- downrevs = self.revision._normalized_down_revisions
-
- if not downrevs:
- # is a base
- return True
- else:
- # none of our downrevs are present, so...
- # we have to insert our version. This is true whether
- # or not there is only one downrev, or multiple (in the latter
- # case, we're a merge point.)
- if not heads.intersection(downrevs):
- return True
- else:
- return False
-
- def should_merge_branches(self, heads: Set[str]) -> bool:
- if not self.is_upgrade:
- return False
-
- downrevs = self.revision._normalized_down_revisions
-
- if len(downrevs) > 1 and len(heads.intersection(downrevs)) > 1:
- return True
-
- return False
-
- def should_unmerge_branches(self, heads: Set[str]) -> bool:
- if not self.is_downgrade:
- return False
-
- downrevs = self.revision._normalized_down_revisions
-
- if self.revision.revision in heads and len(downrevs) > 1:
- return True
-
- return False
-
- def update_version_num(self, heads: Set[str]) -> Tuple[str, str]:
- if not self._has_scalar_down_revision:
- downrev = heads.intersection(
- self.revision._normalized_down_revisions
- )
- assert (
- len(downrev) == 1
- ), "Can't do an UPDATE because downrevision is ambiguous"
- down_revision = list(downrev)[0]
- else:
- down_revision = self.revision._normalized_down_revisions[0]
-
- if self.is_upgrade:
- return down_revision, self.revision.revision
- else:
- return self.revision.revision, down_revision
-
- @property
- def delete_version_num(self) -> str:
- return self.revision.revision
-
- @property
- def insert_version_num(self) -> str:
- return self.revision.revision
-
- @property
- def info(self) -> MigrationInfo:
- return MigrationInfo(
- revision_map=self.revision_map,
- up_revisions=self.revision.revision,
- down_revisions=self.revision._normalized_down_revisions,
- is_upgrade=self.is_upgrade,
- is_stamp=False,
- )
-
-
-class StampStep(MigrationStep):
- def __init__(
- self,
- from_: Optional[Union[str, Collection[str]]],
- to_: Optional[Union[str, Collection[str]]],
- is_upgrade: bool,
- branch_move: bool,
- revision_map: Optional[RevisionMap] = None,
- ) -> None:
- self.from_: Tuple[str, ...] = util.to_tuple(from_, default=())
- self.to_: Tuple[str, ...] = util.to_tuple(to_, default=())
- self.is_upgrade = is_upgrade
- self.branch_move = branch_move
- self.migration_fn = self.stamp_revision
- self.revision_map = revision_map
-
- doc: Optional[str] = None
-
- def stamp_revision(self, **kw: Any) -> None:
- return None
-
- def __eq__(self, other):
- return (
- isinstance(other, StampStep)
- and other.from_revisions == self.from_revisions
- and other.to_revisions == self.to_revisions
- and other.branch_move == self.branch_move
- and self.is_upgrade == other.is_upgrade
- )
-
- @property
- def from_revisions(self):
- return self.from_
-
- @property
- def to_revisions(self) -> Tuple[str, ...]:
- return self.to_
-
- @property
- def from_revisions_no_deps( # type:ignore[override]
- self,
- ) -> Tuple[str, ...]:
- return self.from_
-
- @property
- def to_revisions_no_deps( # type:ignore[override]
- self,
- ) -> Tuple[str, ...]:
- return self.to_
-
- @property
- def delete_version_num(self) -> str:
- assert len(self.from_) == 1
- return self.from_[0]
-
- @property
- def insert_version_num(self) -> str:
- assert len(self.to_) == 1
- return self.to_[0]
-
- def update_version_num(self, heads: Set[str]) -> Tuple[str, str]:
- assert len(self.from_) == 1
- assert len(self.to_) == 1
- return self.from_[0], self.to_[0]
-
- def merge_branch_idents(
- self, heads: Union[Set[str], List[str]]
- ) -> Union[Tuple[List[Any], str, str], Tuple[List[str], str, str]]:
- return (
- # delete revs, update from rev, update to rev
- list(self.from_[0:-1]),
- self.from_[-1],
- self.to_[0],
- )
-
- def unmerge_branch_idents(
- self, heads: Set[str]
- ) -> Tuple[str, str, List[str]]:
- return (
- # update from rev, update to rev, insert revs
- self.from_[0],
- self.to_[-1],
- list(self.to_[0:-1]),
- )
-
- def should_delete_branch(self, heads: Set[str]) -> bool:
- # TODO: we probably need to look for self.to_ inside of heads,
- # in a similar manner as should_create_branch, however we have
- # no tests for this yet (stamp downgrades w/ branches)
- return self.is_downgrade and self.branch_move
-
- def should_create_branch(self, heads: Set[str]) -> Union[Set[str], bool]:
- return (
- self.is_upgrade
- and (self.branch_move or set(self.from_).difference(heads))
- and set(self.to_).difference(heads)
- )
-
- def should_merge_branches(self, heads: Set[str]) -> bool:
- return len(self.from_) > 1
-
- def should_unmerge_branches(self, heads: Set[str]) -> bool:
- return len(self.to_) > 1
-
- @property
- def info(self) -> MigrationInfo:
- up, down = (
- (self.to_, self.from_)
- if self.is_upgrade
- else (self.from_, self.to_)
- )
- assert self.revision_map is not None
- return MigrationInfo(
- revision_map=self.revision_map,
- up_revisions=up,
- down_revisions=down,
- is_upgrade=self.is_upgrade,
- is_stamp=True,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/plugins.py b/backend/.venv/lib/python3.12/site-packages/alembic/runtime/plugins.py
deleted file mode 100644
index be1d590..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/runtime/plugins.py
+++ /dev/null
@@ -1,179 +0,0 @@
-from __future__ import annotations
-
-from importlib import metadata
-import logging
-import re
-from types import ModuleType
-from typing import Callable
-from typing import Pattern
-from typing import TYPE_CHECKING
-
-from .. import util
-from ..util import DispatchPriority
-from ..util import PriorityDispatcher
-
-if TYPE_CHECKING:
- from ..util import PriorityDispatchResult
-
-_all_plugins = {}
-
-
-log = logging.getLogger(__name__)
-
-
-class Plugin:
- """Describe a series of functions that are pulled in as a plugin.
-
- This is initially to provide for portable lists of autogenerate
- comparison functions, however the setup for a plugin can run any
- other kinds of global registration as well.
-
- .. versionadded:: 1.18.0
-
- """
-
- def __init__(self, name: str):
- self.name = name
- log.info("setup plugin %s", name)
- if name in _all_plugins:
- raise ValueError(f"A plugin named {name} is already registered")
- _all_plugins[name] = self
- self.autogenerate_comparators = PriorityDispatcher()
-
- def remove(self) -> None:
- """remove this plugin"""
-
- del _all_plugins[self.name]
-
- def add_autogenerate_comparator(
- self,
- fn: Callable[..., PriorityDispatchResult],
- compare_target: str,
- compare_element: str | None = None,
- *,
- qualifier: str = "default",
- priority: DispatchPriority = DispatchPriority.MEDIUM,
- ) -> None:
- """Register an autogenerate comparison function.
-
- See the section :ref:`plugins_registering_autogenerate` for detailed
- examples on how to use this method.
-
- :param fn: The comparison function to register. The function receives
- arguments specific to the type of comparison being performed and
- should return a :class:`.PriorityDispatchResult` value.
-
- :param compare_target: The type of comparison being performed
- (e.g., ``"table"``, ``"column"``, ``"type"``).
-
- :param compare_element: Optional sub-element being compared within
- the target type.
-
- :param qualifier: Database dialect qualifier. Use ``"default"`` for
- all dialects, or specify a dialect name like ``"postgresql"`` to
- register a dialect-specific handler. Defaults to ``"default"``.
-
- :param priority: Execution priority for this comparison function.
- Functions are executed in priority order from
- :attr:`.DispatchPriority.FIRST` to :attr:`.DispatchPriority.LAST`.
- Defaults to :attr:`.DispatchPriority.MEDIUM`.
-
- """
- self.autogenerate_comparators.dispatch_for(
- compare_target,
- subgroup=compare_element,
- priority=priority,
- qualifier=qualifier,
- )(fn)
-
- @classmethod
- def populate_autogenerate_priority_dispatch(
- cls, comparators: PriorityDispatcher, include_plugins: list[str]
- ) -> None:
- """Populate all current autogenerate comparison functions into
- a given PriorityDispatcher."""
-
- exclude: set[Pattern[str]] = set()
- include: dict[str, Pattern[str]] = {}
-
- matched_expressions: set[str] = set()
-
- for name in include_plugins:
- if name.startswith("~"):
- exclude.add(_make_re(name[1:]))
- else:
- include[name] = _make_re(name)
-
- for plugin in _all_plugins.values():
- if any(excl.match(plugin.name) for excl in exclude):
- continue
-
- include_matches = [
- incl for incl in include if include[incl].match(plugin.name)
- ]
- if not include_matches:
- continue
- else:
- matched_expressions.update(include_matches)
-
- log.info("setting up autogenerate plugin %s", plugin.name)
- comparators.populate_with(plugin.autogenerate_comparators)
-
- never_matched = set(include).difference(matched_expressions)
- if never_matched:
- raise util.CommandError(
- f"Did not locate plugins: {', '.join(never_matched)}"
- )
-
- @classmethod
- def setup_plugin_from_module(cls, module: ModuleType, name: str) -> None:
- """Call the ``setup()`` function of a plugin module, identified by
- passing the module object itself.
-
- E.g.::
-
- from alembic.runtime.plugins import Plugin
- import myproject.alembic_plugin
-
- # Register the plugin manually
- Plugin.setup_plugin_from_module(
- myproject.alembic_plugin,
- "myproject.custom_operations"
- )
-
- This will generate a new :class:`.Plugin` object with the given
- name, which will register itself in the global list of plugins.
- Then the module's ``setup()`` function is invoked, passing that
- :class:`.Plugin` object.
-
- This exact process is invoked automatically at import time for any
- plugin module that is published via the ``alembic.plugins`` entrypoint.
-
- """
- module.setup(Plugin(name))
-
-
-def _make_re(name: str) -> Pattern[str]:
- tokens = name.split(".")
-
- reg = r""
- for token in tokens:
- if token == "*":
- reg += r"\..+?"
- elif token.isidentifier():
- reg += r"\." + token
- else:
- raise ValueError(f"Invalid plugin expression {name!r}")
-
- # omit leading r'\.'
- return re.compile(f"^{reg[2:]}$")
-
-
-def _setup() -> None:
- # setup third party plugins
- for entrypoint in metadata.entry_points(group="alembic.plugins"):
- for mod in entrypoint.load():
- Plugin.setup_plugin_from_module(mod, entrypoint.name)
-
-
-_setup()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/script/__init__.py
deleted file mode 100644
index d78f3f1..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/script/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .base import Script
-from .base import ScriptDirectory
-
-__all__ = ["ScriptDirectory", "Script"]
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 62bd855..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index 2dc88ea..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc
deleted file mode 100644
index d676110..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc
deleted file mode 100644
index 1adaae4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/base.py b/backend/.venv/lib/python3.12/site-packages/alembic/script/base.py
deleted file mode 100644
index f841708..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/script/base.py
+++ /dev/null
@@ -1,1052 +0,0 @@
-from __future__ import annotations
-
-from contextlib import contextmanager
-import datetime
-import os
-from pathlib import Path
-import re
-import shutil
-import sys
-from types import ModuleType
-from typing import Any
-from typing import cast
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-from . import revision
-from . import write_hooks
-from .. import util
-from ..runtime import migration
-from ..util import compat
-from ..util import not_none
-from ..util.pyfiles import _preserving_path_as_str
-
-if TYPE_CHECKING:
- from .revision import _GetRevArg
- from .revision import _RevIdType
- from .revision import Revision
- from ..config import Config
- from ..config import MessagingOptions
- from ..config import PostWriteHookConfig
- from ..runtime.migration import RevisionStep
- from ..runtime.migration import StampStep
-
-try:
- from zoneinfo import ZoneInfo
- from zoneinfo import ZoneInfoNotFoundError
-except ImportError:
- ZoneInfo = None # type: ignore[assignment, misc]
-
-_sourceless_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)(c|o)?$")
-_only_source_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)$")
-_legacy_rev = re.compile(r"([a-f0-9]+)\.py$")
-_slug_re = re.compile(r"\w+")
-_default_file_template = "%(rev)s_%(slug)s"
-
-
-class ScriptDirectory:
- """Provides operations upon an Alembic script directory.
-
- This object is useful to get information as to current revisions,
- most notably being able to get at the "head" revision, for schemes
- that want to test if the current revision in the database is the most
- recent::
-
- from alembic.script import ScriptDirectory
- from alembic.config import Config
- config = Config()
- config.set_main_option("script_location", "myapp:migrations")
- script = ScriptDirectory.from_config(config)
-
- head_revision = script.get_current_head()
-
-
-
- """
-
- def __init__(
- self,
- dir: Union[str, os.PathLike[str]], # noqa: A002
- file_template: str = _default_file_template,
- truncate_slug_length: Optional[int] = 40,
- version_locations: Optional[
- Sequence[Union[str, os.PathLike[str]]]
- ] = None,
- sourceless: bool = False,
- output_encoding: str = "utf-8",
- timezone: Optional[str] = None,
- hooks: list[PostWriteHookConfig] = [],
- recursive_version_locations: bool = False,
- messaging_opts: MessagingOptions = cast(
- "MessagingOptions", util.EMPTY_DICT
- ),
- ) -> None:
- self.dir = _preserving_path_as_str(dir)
- self.version_locations = [
- _preserving_path_as_str(p) for p in version_locations or ()
- ]
- self.file_template = file_template
- self.truncate_slug_length = truncate_slug_length or 40
- self.sourceless = sourceless
- self.output_encoding = output_encoding
- self.revision_map = revision.RevisionMap(self._load_revisions)
- self.timezone = timezone
- self.hooks = hooks
- self.recursive_version_locations = recursive_version_locations
- self.messaging_opts = messaging_opts
-
- if not os.access(dir, os.F_OK):
- raise util.CommandError(
- f"Path doesn't exist: {dir}. Please use "
- "the 'init' command to create a new "
- "scripts folder."
- )
-
- @property
- def versions(self) -> str:
- """return a single version location based on the sole path passed
- within version_locations.
-
- If multiple version locations are configured, an error is raised.
-
-
- """
- return str(self._singular_version_location)
-
- @util.memoized_property
- def _singular_version_location(self) -> Path:
- loc = self._version_locations
- if len(loc) > 1:
- raise util.CommandError("Multiple version_locations present")
- else:
- return loc[0]
-
- @util.memoized_property
- def _version_locations(self) -> Sequence[Path]:
- if self.version_locations:
- return [
- util.coerce_resource_to_filename(location).absolute()
- for location in self.version_locations
- ]
- else:
- return [Path(self.dir, "versions").absolute()]
-
- def _load_revisions(self) -> Iterator[Script]:
- paths = [vers for vers in self._version_locations if vers.exists()]
-
- dupes = set()
- for vers in paths:
- for file_path in Script._list_py_dir(self, vers):
- real_path = file_path.resolve()
- if real_path in dupes:
- util.warn(
- f"File {real_path} loaded twice! ignoring. "
- "Please ensure version_locations is unique."
- )
- continue
- dupes.add(real_path)
-
- script = Script._from_path(self, real_path)
- if script is None:
- continue
- yield script
-
- @classmethod
- def from_config(cls, config: Config) -> ScriptDirectory:
- """Produce a new :class:`.ScriptDirectory` given a :class:`.Config`
- instance.
-
- The :class:`.Config` need only have the ``script_location`` key
- present.
-
- """
- script_location = config.get_alembic_option("script_location")
- if script_location is None:
- raise util.CommandError(
- "No 'script_location' key found in configuration."
- )
- truncate_slug_length: Optional[int]
- tsl = config.get_alembic_option("truncate_slug_length")
- if tsl is not None:
- truncate_slug_length = int(tsl)
- else:
- truncate_slug_length = None
-
- prepend_sys_path = config.get_prepend_sys_paths_list()
- if prepend_sys_path:
- sys.path[:0] = prepend_sys_path
-
- rvl = config.get_alembic_boolean_option("recursive_version_locations")
- return ScriptDirectory(
- util.coerce_resource_to_filename(script_location),
- file_template=config.get_alembic_option(
- "file_template", _default_file_template
- ),
- truncate_slug_length=truncate_slug_length,
- sourceless=config.get_alembic_boolean_option("sourceless"),
- output_encoding=config.get_alembic_option(
- "output_encoding", "utf-8"
- ),
- version_locations=config.get_version_locations_list(),
- timezone=config.get_alembic_option("timezone"),
- hooks=config.get_hooks_list(),
- recursive_version_locations=rvl,
- messaging_opts=config.messaging_opts,
- )
-
- @contextmanager
- def _catch_revision_errors(
- self,
- ancestor: Optional[str] = None,
- multiple_heads: Optional[str] = None,
- start: Optional[str] = None,
- end: Optional[str] = None,
- resolution: Optional[str] = None,
- ) -> Iterator[None]:
- try:
- yield
- except revision.RangeNotAncestorError as rna:
- if start is None:
- start = cast(Any, rna.lower)
- if end is None:
- end = cast(Any, rna.upper)
- if not ancestor:
- ancestor = (
- "Requested range %(start)s:%(end)s does not refer to "
- "ancestor/descendant revisions along the same branch"
- )
- ancestor = ancestor % {"start": start, "end": end}
- raise util.CommandError(ancestor) from rna
- except revision.MultipleHeads as mh:
- if not multiple_heads:
- multiple_heads = (
- "Multiple head revisions are present for given "
- "argument '%(head_arg)s'; please "
- "specify a specific target revision, "
- "'@%(head_arg)s' to "
- "narrow to a specific head, or 'heads' for all heads"
- )
- multiple_heads = multiple_heads % {
- "head_arg": end or mh.argument,
- "heads": util.format_as_comma(mh.heads),
- }
- raise util.CommandError(multiple_heads) from mh
- except revision.ResolutionError as re:
- if resolution is None:
- resolution = "Can't locate revision identified by '%s'" % (
- re.argument
- )
- raise util.CommandError(resolution) from re
- except revision.RevisionError as err:
- raise util.CommandError(err.args[0]) from err
-
- def walk_revisions(
- self, base: str = "base", head: str = "heads"
- ) -> Iterator[Script]:
- """Iterate through all revisions.
-
- :param base: the base revision, or "base" to start from the
- empty revision.
-
- :param head: the head revision; defaults to "heads" to indicate
- all head revisions. May also be "head" to indicate a single
- head revision.
-
- """
- with self._catch_revision_errors(start=base, end=head):
- for rev in self.revision_map.iterate_revisions(
- head, base, inclusive=True, assert_relative_length=False
- ):
- yield cast(Script, rev)
-
- def get_revisions(self, id_: _GetRevArg) -> Tuple[Script, ...]:
- """Return the :class:`.Script` instance with the given rev identifier,
- symbolic name, or sequence of identifiers.
-
- """
- with self._catch_revision_errors():
- return cast(
- Tuple[Script, ...],
- self.revision_map.get_revisions(id_),
- )
-
- def get_all_current(self, id_: Tuple[str, ...]) -> Set[Script]:
- with self._catch_revision_errors():
- return cast(Set[Script], self.revision_map._get_all_current(id_))
-
- def get_revision(self, id_: str) -> Script:
- """Return the :class:`.Script` instance with the given rev id.
-
- .. seealso::
-
- :meth:`.ScriptDirectory.get_revisions`
-
- """
-
- with self._catch_revision_errors():
- return cast(Script, self.revision_map.get_revision(id_))
-
- def as_revision_number(
- self, id_: Optional[str]
- ) -> Optional[Union[str, Tuple[str, ...]]]:
- """Convert a symbolic revision, i.e. 'head' or 'base', into
- an actual revision number."""
-
- with self._catch_revision_errors():
- rev, branch_name = self.revision_map._resolve_revision_number(id_)
-
- if not rev:
- # convert () to None
- return None
- elif id_ == "heads":
- return rev
- else:
- return rev[0]
-
- def iterate_revisions(
- self,
- upper: Union[str, Tuple[str, ...], None],
- lower: Union[str, Tuple[str, ...], None],
- **kw: Any,
- ) -> Iterator[Script]:
- """Iterate through script revisions, starting at the given
- upper revision identifier and ending at the lower.
-
- The traversal uses strictly the `down_revision`
- marker inside each migration script, so
- it is a requirement that upper >= lower,
- else you'll get nothing back.
-
- The iterator yields :class:`.Script` objects.
-
- .. seealso::
-
- :meth:`.RevisionMap.iterate_revisions`
-
- """
- return cast(
- Iterator[Script],
- self.revision_map.iterate_revisions(upper, lower, **kw),
- )
-
- def get_current_head(self) -> Optional[str]:
- """Return the current head revision.
-
- If the script directory has multiple heads
- due to branching, an error is raised;
- :meth:`.ScriptDirectory.get_heads` should be
- preferred.
-
- :return: a string revision number.
-
- .. seealso::
-
- :meth:`.ScriptDirectory.get_heads`
-
- """
- with self._catch_revision_errors(
- multiple_heads=(
- "The script directory has multiple heads (due to branching)."
- "Please use get_heads(), or merge the branches using "
- "alembic merge."
- )
- ):
- return self.revision_map.get_current_head()
-
- def get_heads(self) -> List[str]:
- """Return all "versioned head" revisions as strings.
-
- This is normally a list of length one,
- unless branches are present. The
- :meth:`.ScriptDirectory.get_current_head()` method
- can be used normally when a script directory
- has only one head.
-
- :return: a tuple of string revision numbers.
- """
- return list(self.revision_map.heads)
-
- def get_base(self) -> Optional[str]:
- """Return the "base" revision as a string.
-
- This is the revision number of the script that
- has a ``down_revision`` of None.
-
- If the script directory has multiple bases, an error is raised;
- :meth:`.ScriptDirectory.get_bases` should be
- preferred.
-
- """
- bases = self.get_bases()
- if len(bases) > 1:
- raise util.CommandError(
- "The script directory has multiple bases. "
- "Please use get_bases()."
- )
- elif bases:
- return bases[0]
- else:
- return None
-
- def get_bases(self) -> List[str]:
- """return all "base" revisions as strings.
-
- This is the revision number of all scripts that
- have a ``down_revision`` of None.
-
- """
- return list(self.revision_map.bases)
-
- def _upgrade_revs(
- self, destination: str, current_rev: str
- ) -> List[RevisionStep]:
- with self._catch_revision_errors(
- ancestor="Destination %(end)s is not a valid upgrade "
- "target from current head(s)",
- end=destination,
- ):
- revs = self.iterate_revisions(
- destination, current_rev, implicit_base=True
- )
- return [
- migration.MigrationStep.upgrade_from_script(
- self.revision_map, script
- )
- for script in reversed(list(revs))
- ]
-
- def _downgrade_revs(
- self, destination: str, current_rev: Optional[str]
- ) -> List[RevisionStep]:
- with self._catch_revision_errors(
- ancestor="Destination %(end)s is not a valid downgrade "
- "target from current head(s)",
- end=destination,
- ):
- revs = self.iterate_revisions(
- current_rev, destination, select_for_downgrade=True
- )
- return [
- migration.MigrationStep.downgrade_from_script(
- self.revision_map, script
- )
- for script in revs
- ]
-
- def _stamp_revs(
- self, revision: _RevIdType, heads: _RevIdType
- ) -> List[StampStep]:
- with self._catch_revision_errors(
- multiple_heads="Multiple heads are present; please specify a "
- "single target revision"
- ):
- heads_revs = self.get_revisions(heads)
-
- steps = []
-
- if not revision:
- revision = "base"
-
- filtered_heads: List[Script] = []
- for rev in util.to_tuple(revision):
- if rev:
- filtered_heads.extend(
- self.revision_map.filter_for_lineage(
- cast(Sequence[Script], heads_revs),
- rev,
- include_dependencies=True,
- )
- )
- filtered_heads = util.unique_list(filtered_heads)
-
- dests = self.get_revisions(revision) or [None]
-
- for dest in dests:
- if dest is None:
- # dest is 'base'. Return a "delete branch" migration
- # for all applicable heads.
- steps.extend(
- [
- migration.StampStep(
- head.revision,
- None,
- False,
- True,
- self.revision_map,
- )
- for head in filtered_heads
- ]
- )
- continue
- elif dest in filtered_heads:
- # the dest is already in the version table, do nothing.
- continue
-
- # figure out if the dest is a descendant or an
- # ancestor of the selected nodes
- descendants = set(
- self.revision_map._get_descendant_nodes([dest])
- )
- ancestors = set(self.revision_map._get_ancestor_nodes([dest]))
-
- if descendants.intersection(filtered_heads):
- # heads are above the target, so this is a downgrade.
- # we can treat them as a "merge", single step.
- assert not ancestors.intersection(filtered_heads)
- todo_heads = [head.revision for head in filtered_heads]
- step = migration.StampStep(
- todo_heads,
- dest.revision,
- False,
- False,
- self.revision_map,
- )
- steps.append(step)
- continue
- elif ancestors.intersection(filtered_heads):
- # heads are below the target, so this is an upgrade.
- # we can treat them as a "merge", single step.
- todo_heads = [head.revision for head in filtered_heads]
- step = migration.StampStep(
- todo_heads,
- dest.revision,
- True,
- False,
- self.revision_map,
- )
- steps.append(step)
- continue
- else:
- # destination is in a branch not represented,
- # treat it as new branch
- step = migration.StampStep(
- (), dest.revision, True, True, self.revision_map
- )
- steps.append(step)
- continue
-
- return steps
-
- def run_env(self) -> None:
- """Run the script environment.
-
- This basically runs the ``env.py`` script present
- in the migration environment. It is called exclusively
- by the command functions in :mod:`alembic.command`.
-
-
- """
- util.load_python_file(self.dir, "env.py")
-
- @property
- def env_py_location(self) -> str:
- return str(Path(self.dir, "env.py"))
-
- def _append_template(self, src: Path, dest: Path, **kw: Any) -> None:
- with util.status(
- f"Appending to existing {dest.absolute()}",
- **self.messaging_opts,
- ):
- util.template_to_file(
- src,
- dest,
- self.output_encoding,
- append_with_newlines=True,
- **kw,
- )
-
- def _generate_template(self, src: Path, dest: Path, **kw: Any) -> None:
- with util.status(
- f"Generating {dest.absolute()}", **self.messaging_opts
- ):
- util.template_to_file(src, dest, self.output_encoding, **kw)
-
- def _copy_file(self, src: Path, dest: Path) -> None:
- with util.status(
- f"Generating {dest.absolute()}", **self.messaging_opts
- ):
- shutil.copy(src, dest)
-
- def _ensure_directory(self, path: Path) -> None:
- path = path.absolute()
- if not path.exists():
- with util.status(
- f"Creating directory {path}", **self.messaging_opts
- ):
- os.makedirs(path)
-
- def _generate_create_date(self) -> datetime.datetime:
- if self.timezone is not None:
- if ZoneInfo is None:
- raise util.CommandError(
- "Python >= 3.9 is required for timezone support or "
- "the 'backports.zoneinfo' package must be installed."
- )
- # First, assume correct capitalization
- try:
- tzinfo = ZoneInfo(self.timezone)
- except ZoneInfoNotFoundError:
- tzinfo = None
- if tzinfo is None:
- try:
- tzinfo = ZoneInfo(self.timezone.upper())
- except ZoneInfoNotFoundError:
- raise util.CommandError(
- "Can't locate timezone: %s" % self.timezone
- ) from None
-
- create_date = datetime.datetime.now(
- tz=datetime.timezone.utc
- ).astimezone(tzinfo)
- else:
- create_date = datetime.datetime.now()
- return create_date
-
- def generate_revision(
- self,
- revid: str,
- message: Optional[str],
- head: Optional[_RevIdType] = None,
- splice: Optional[bool] = False,
- branch_labels: Optional[_RevIdType] = None,
- version_path: Union[str, os.PathLike[str], None] = None,
- file_template: Optional[str] = None,
- depends_on: Optional[_RevIdType] = None,
- **kw: Any,
- ) -> Optional[Script]:
- """Generate a new revision file.
-
- This runs the ``script.py.mako`` template, given
- template arguments, and creates a new file.
-
- :param revid: String revision id. Typically this
- comes from ``alembic.util.rev_id()``.
- :param message: the revision message, the one passed
- by the -m argument to the ``revision`` command.
- :param head: the head revision to generate against. Defaults
- to the current "head" if no branches are present, else raises
- an exception.
- :param splice: if True, allow the "head" version to not be an
- actual head; otherwise, the selected head must be a head
- (e.g. endpoint) revision.
-
- """
- if head is None:
- head = "head"
-
- try:
- Script.verify_rev_id(revid)
- except revision.RevisionError as err:
- raise util.CommandError(err.args[0]) from err
-
- with self._catch_revision_errors(
- multiple_heads=(
- "Multiple heads are present; please specify the head "
- "revision on which the new revision should be based, "
- "or perform a merge."
- )
- ):
- heads = cast(
- Tuple[Optional["Revision"], ...],
- self.revision_map.get_revisions(head),
- )
- for h in heads:
- assert h != "base" # type: ignore[comparison-overlap]
-
- if len(set(heads)) != len(heads):
- raise util.CommandError("Duplicate head revisions specified")
-
- create_date = self._generate_create_date()
-
- if version_path is None:
- if len(self._version_locations) > 1:
- for head_ in heads:
- if head_ is not None:
- assert isinstance(head_, Script)
- version_path = head_._script_path.parent
- break
- else:
- raise util.CommandError(
- "Multiple version locations present, "
- "please specify --version-path"
- )
- else:
- version_path = self._singular_version_location
- else:
- version_path = Path(version_path)
-
- assert isinstance(version_path, Path)
- norm_path = version_path.absolute()
- for vers_path in self._version_locations:
- if vers_path.absolute() == norm_path:
- break
- else:
- raise util.CommandError(
- f"Path {version_path} is not represented in current "
- "version locations"
- )
-
- if self.version_locations:
- self._ensure_directory(version_path)
-
- path = self._rev_path(version_path, revid, message, create_date)
- self._ensure_directory(path.parent)
-
- if not splice:
- for head_ in heads:
- if head_ is not None and not head_.is_head:
- raise util.CommandError(
- "Revision %s is not a head revision; please specify "
- "--splice to create a new branch from this revision"
- % head_.revision
- )
-
- resolved_depends_on: Optional[List[str]]
- if depends_on:
- with self._catch_revision_errors():
- resolved_depends_on = [
- (
- dep
- if dep in rev.branch_labels # maintain branch labels
- else rev.revision
- ) # resolve partial revision identifiers
- for rev, dep in [
- (not_none(self.revision_map.get_revision(dep)), dep)
- for dep in util.to_list(depends_on)
- ]
- ]
- else:
- resolved_depends_on = None
-
- self._generate_template(
- Path(self.dir, "script.py.mako"),
- path,
- up_revision=str(revid),
- down_revision=revision.tuple_rev_as_scalar(
- tuple(h.revision if h is not None else None for h in heads)
- ),
- branch_labels=util.to_tuple(branch_labels),
- depends_on=revision.tuple_rev_as_scalar(resolved_depends_on),
- create_date=create_date,
- comma=util.format_as_comma,
- message=message if message is not None else ("empty message"),
- **kw,
- )
-
- post_write_hooks = self.hooks
- if post_write_hooks:
- write_hooks._run_hooks(path, post_write_hooks)
-
- try:
- script = Script._from_path(self, path)
- except revision.RevisionError as err:
- raise util.CommandError(err.args[0]) from err
- if script is None:
- return None
- if branch_labels and not script.branch_labels:
- raise util.CommandError(
- "Version %s specified branch_labels %s, however the "
- "migration file %s does not have them; have you upgraded "
- "your script.py.mako to include the "
- "'branch_labels' section?"
- % (script.revision, branch_labels, script.path)
- )
- self.revision_map.add_revision(script)
- return script
-
- def _rev_path(
- self,
- path: Union[str, os.PathLike[str]],
- rev_id: str,
- message: Optional[str],
- create_date: datetime.datetime,
- ) -> Path:
- epoch = int(create_date.timestamp())
- slug = "_".join(_slug_re.findall(message or "")).lower()
- if len(slug) > self.truncate_slug_length:
- slug = slug[: self.truncate_slug_length].rsplit("_", 1)[0] + "_"
- filename = "%s.py" % (
- self.file_template
- % {
- "rev": rev_id,
- "slug": slug,
- "epoch": epoch,
- "year": create_date.year,
- "month": create_date.month,
- "day": create_date.day,
- "hour": create_date.hour,
- "minute": create_date.minute,
- "second": create_date.second,
- }
- )
- return Path(path) / filename
-
-
-class Script(revision.Revision):
- """Represent a single revision file in a ``versions/`` directory.
-
- The :class:`.Script` instance is returned by methods
- such as :meth:`.ScriptDirectory.iterate_revisions`.
-
- """
-
- def __init__(
- self,
- module: ModuleType,
- rev_id: str,
- path: Union[str, os.PathLike[str]],
- ):
- self.module = module
- self.path = _preserving_path_as_str(path)
- super().__init__(
- rev_id,
- module.down_revision,
- branch_labels=util.to_tuple(
- getattr(module, "branch_labels", None), default=()
- ),
- dependencies=util.to_tuple(
- getattr(module, "depends_on", None), default=()
- ),
- )
-
- module: ModuleType
- """The Python module representing the actual script itself."""
-
- path: str
- """Filesystem path of the script."""
-
- @property
- def _script_path(self) -> Path:
- return Path(self.path)
-
- _db_current_indicator: Optional[bool] = None
- """Utility variable which when set will cause string output to indicate
- this is a "current" version in some database"""
-
- @property
- def doc(self) -> str:
- """Return the docstring given in the script."""
-
- return re.split("\n\n", self.longdoc)[0]
-
- @property
- def longdoc(self) -> str:
- """Return the docstring given in the script."""
-
- doc = self.module.__doc__
- if doc:
- if hasattr(self.module, "_alembic_source_encoding"):
- doc = doc.decode( # type: ignore[attr-defined]
- self.module._alembic_source_encoding
- )
- return doc.strip()
- else:
- return ""
-
- @property
- def log_entry(self) -> str:
- entry = "Rev: %s%s%s%s%s\n" % (
- self.revision,
- " (head)" if self.is_head else "",
- " (branchpoint)" if self.is_branch_point else "",
- " (mergepoint)" if self.is_merge_point else "",
- " (current)" if self._db_current_indicator else "",
- )
- if self.is_merge_point:
- entry += "Merges: %s\n" % (self._format_down_revision(),)
- else:
- entry += "Parent: %s\n" % (self._format_down_revision(),)
-
- if self.dependencies:
- entry += "Also depends on: %s\n" % (
- util.format_as_comma(self.dependencies)
- )
-
- if self.is_branch_point:
- entry += "Branches into: %s\n" % (
- util.format_as_comma(self.nextrev)
- )
-
- if self.branch_labels:
- entry += "Branch names: %s\n" % (
- util.format_as_comma(self.branch_labels),
- )
-
- entry += "Path: %s\n" % (self.path,)
-
- entry += "\n%s\n" % (
- "\n".join(" %s" % para for para in self.longdoc.splitlines())
- )
- return entry
-
- def __str__(self) -> str:
- return "%s -> %s%s%s%s, %s" % (
- self._format_down_revision(),
- self.revision,
- " (head)" if self.is_head else "",
- " (branchpoint)" if self.is_branch_point else "",
- " (mergepoint)" if self.is_merge_point else "",
- self.doc,
- )
-
- def _head_only(
- self,
- include_branches: bool = False,
- include_doc: bool = False,
- include_parents: bool = False,
- tree_indicators: bool = True,
- head_indicators: bool = True,
- ) -> str:
- text = self.revision
- if include_parents:
- if self.dependencies:
- text = "%s (%s) -> %s" % (
- self._format_down_revision(),
- util.format_as_comma(self.dependencies),
- text,
- )
- else:
- text = "%s -> %s" % (self._format_down_revision(), text)
- assert text is not None
- if include_branches and self.branch_labels:
- text += " (%s)" % util.format_as_comma(self.branch_labels)
- if head_indicators or tree_indicators:
- text += "%s%s%s" % (
- " (head)" if self._is_real_head else "",
- (
- " (effective head)"
- if self.is_head and not self._is_real_head
- else ""
- ),
- " (current)" if self._db_current_indicator else "",
- )
- if tree_indicators:
- text += "%s%s" % (
- " (branchpoint)" if self.is_branch_point else "",
- " (mergepoint)" if self.is_merge_point else "",
- )
- if include_doc:
- text += ", %s" % self.doc
- return text
-
- def cmd_format(
- self,
- verbose: bool,
- include_branches: bool = False,
- include_doc: bool = False,
- include_parents: bool = False,
- tree_indicators: bool = True,
- ) -> str:
- if verbose:
- return self.log_entry
- else:
- return self._head_only(
- include_branches, include_doc, include_parents, tree_indicators
- )
-
- def _format_down_revision(self) -> str:
- if not self.down_revision:
- return ""
- else:
- return util.format_as_comma(self._versioned_down_revisions)
-
- @classmethod
- def _list_py_dir(
- cls, scriptdir: ScriptDirectory, path: Path
- ) -> List[Path]:
- paths = []
- for root, dirs, files in compat.path_walk(path, top_down=True):
- if root.name.endswith("__pycache__"):
- # a special case - we may include these files
- # if a `sourceless` option is specified
- continue
-
- for filename in sorted(files):
- paths.append(root / filename)
-
- if scriptdir.sourceless:
- # look for __pycache__
- py_cache_path = root / "__pycache__"
- if py_cache_path.exists():
- # add all files from __pycache__ whose filename is not
- # already in the names we got from the version directory.
- # add as relative paths including __pycache__ token
- names = {
- Path(filename).name.split(".")[0] for filename in files
- }
- paths.extend(
- py_cache_path / pyc
- for pyc in py_cache_path.iterdir()
- if pyc.name.split(".")[0] not in names
- )
-
- if not scriptdir.recursive_version_locations:
- break
-
- # the real script order is defined by revision,
- # but it may be undefined if there are many files with a same
- # `down_revision`, for a better user experience (ex. debugging),
- # we use a deterministic order
- dirs.sort()
-
- return paths
-
- @classmethod
- def _from_path(
- cls, scriptdir: ScriptDirectory, path: Union[str, os.PathLike[str]]
- ) -> Optional[Script]:
-
- path = Path(path)
- dir_, filename = path.parent, path.name
-
- if scriptdir.sourceless:
- py_match = _sourceless_rev_file.match(filename)
- else:
- py_match = _only_source_rev_file.match(filename)
-
- if not py_match:
- return None
-
- py_filename = py_match.group(1)
-
- if scriptdir.sourceless:
- is_c = py_match.group(2) == "c"
- is_o = py_match.group(2) == "o"
- else:
- is_c = is_o = False
-
- if is_o or is_c:
- py_exists = (dir_ / py_filename).exists()
- pyc_exists = (dir_ / (py_filename + "c")).exists()
-
- # prefer .py over .pyc because we'd like to get the
- # source encoding; prefer .pyc over .pyo because we'd like to
- # have the docstrings which a -OO file would not have
- if py_exists or is_o and pyc_exists:
- return None
-
- module = util.load_python_file(dir_, filename)
-
- if not hasattr(module, "revision"):
- # attempt to get the revision id from the script name,
- # this for legacy only
- m = _legacy_rev.match(filename)
- if not m:
- raise util.CommandError(
- "Could not determine revision id from "
- f"filename {filename}. "
- "Be sure the 'revision' variable is "
- "declared inside the script (please see 'Upgrading "
- "from Alembic 0.1 to 0.2' in the documentation)."
- )
- else:
- revision = m.group(1)
- else:
- revision = module.revision
- return Script(module, revision, dir_ / filename)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/revision.py b/backend/.venv/lib/python3.12/site-packages/alembic/script/revision.py
deleted file mode 100644
index 5825da3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/script/revision.py
+++ /dev/null
@@ -1,1728 +0,0 @@
-from __future__ import annotations
-
-import collections
-import re
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Collection
-from typing import Deque
-from typing import Dict
-from typing import FrozenSet
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import overload
-from typing import Protocol
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy import util as sqlautil
-
-from .. import util
-from ..util import not_none
-
-if TYPE_CHECKING:
- from typing import Literal
-
-_RevIdType = Union[str, List[str], Tuple[str, ...]]
-_GetRevArg = Union[
- str,
- Iterable[Optional[str]],
- Iterable[str],
-]
-_RevisionIdentifierType = Union[str, Tuple[str, ...], None]
-_RevisionOrStr = Union["Revision", str]
-_RevisionOrBase = Union["Revision", "Literal['base']"]
-_InterimRevisionMapType = Dict[str, "Revision"]
-_RevisionMapType = Dict[Union[None, str, Tuple[()]], Optional["Revision"]]
-_T = TypeVar("_T")
-_TR = TypeVar("_TR", bound=Optional[_RevisionOrStr])
-
-_relative_destination = re.compile(r"(?:(.+?)@)?(\w+)?((?:\+|-)\d+)")
-_revision_illegal_chars = ["@", "-", "+", ":"]
-
-
-class _CollectRevisionsProtocol(Protocol):
- def __call__(
- self,
- upper: _RevisionIdentifierType,
- lower: _RevisionIdentifierType,
- inclusive: bool,
- implicit_base: bool,
- assert_relative_length: bool,
- ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: ...
-
-
-class RevisionError(Exception):
- pass
-
-
-class RangeNotAncestorError(RevisionError):
- def __init__(
- self, lower: _RevisionIdentifierType, upper: _RevisionIdentifierType
- ) -> None:
- self.lower = lower
- self.upper = upper
- super().__init__(
- "Revision %s is not an ancestor of revision %s"
- % (lower or "base", upper or "base")
- )
-
-
-class MultipleHeads(RevisionError):
- def __init__(self, heads: Sequence[str], argument: Optional[str]) -> None:
- self.heads = heads
- self.argument = argument
- super().__init__(
- "Multiple heads are present for given argument '%s'; "
- "%s" % (argument, ", ".join(heads))
- )
-
-
-class ResolutionError(RevisionError):
- def __init__(self, message: str, argument: str) -> None:
- super().__init__(message)
- self.argument = argument
-
-
-class CycleDetected(RevisionError):
- kind = "Cycle"
-
- def __init__(self, revisions: Sequence[str]) -> None:
- self.revisions = revisions
- super().__init__(
- "%s is detected in revisions (%s)"
- % (self.kind, ", ".join(revisions))
- )
-
-
-class DependencyCycleDetected(CycleDetected):
- kind = "Dependency cycle"
-
- def __init__(self, revisions: Sequence[str]) -> None:
- super().__init__(revisions)
-
-
-class LoopDetected(CycleDetected):
- kind = "Self-loop"
-
- def __init__(self, revision: str) -> None:
- super().__init__([revision])
-
-
-class DependencyLoopDetected(DependencyCycleDetected, LoopDetected):
- kind = "Dependency self-loop"
-
- def __init__(self, revision: Sequence[str]) -> None:
- super().__init__(revision)
-
-
-class RevisionMap:
- """Maintains a map of :class:`.Revision` objects.
-
- :class:`.RevisionMap` is used by :class:`.ScriptDirectory` to maintain
- and traverse the collection of :class:`.Script` objects, which are
- themselves instances of :class:`.Revision`.
-
- """
-
- def __init__(self, generator: Callable[[], Iterable[Revision]]) -> None:
- """Construct a new :class:`.RevisionMap`.
-
- :param generator: a zero-arg callable that will generate an iterable
- of :class:`.Revision` instances to be used. These are typically
- :class:`.Script` subclasses within regular Alembic use.
-
- """
- self._generator = generator
-
- @util.memoized_property
- def heads(self) -> Tuple[str, ...]:
- """All "head" revisions as strings.
-
- This is normally a tuple of length one,
- unless unmerged branches are present.
-
- :return: a tuple of string revision numbers.
-
- """
- self._revision_map
- return self.heads
-
- @util.memoized_property
- def bases(self) -> Tuple[str, ...]:
- """All "base" revisions as strings.
-
- These are revisions that have a ``down_revision`` of None,
- or empty tuple.
-
- :return: a tuple of string revision numbers.
-
- """
- self._revision_map
- return self.bases
-
- @util.memoized_property
- def _real_heads(self) -> Tuple[str, ...]:
- """All "real" head revisions as strings.
-
- :return: a tuple of string revision numbers.
-
- """
- self._revision_map
- return self._real_heads
-
- @util.memoized_property
- def _real_bases(self) -> Tuple[str, ...]:
- """All "real" base revisions as strings.
-
- :return: a tuple of string revision numbers.
-
- """
- self._revision_map
- return self._real_bases
-
- @util.memoized_property
- def _revision_map(self) -> _RevisionMapType:
- """memoized attribute, initializes the revision map from the
- initial collection.
-
- """
- # Ordering required for some tests to pass (but not required in
- # general)
- map_: _InterimRevisionMapType = sqlautil.OrderedDict()
-
- heads: Set[Revision] = sqlautil.OrderedSet()
- _real_heads: Set[Revision] = sqlautil.OrderedSet()
- bases: Tuple[Revision, ...] = ()
- _real_bases: Tuple[Revision, ...] = ()
-
- has_branch_labels = set()
- all_revisions = set()
-
- for revision in self._generator():
- all_revisions.add(revision)
-
- if revision.revision in map_:
- util.warn(
- "Revision %s is present more than once" % revision.revision
- )
- map_[revision.revision] = revision
- if revision.branch_labels:
- has_branch_labels.add(revision)
-
- heads.add(revision)
- _real_heads.add(revision)
- if revision.is_base:
- bases += (revision,)
- if revision._is_real_base:
- _real_bases += (revision,)
-
- # add the branch_labels to the map_. We'll need these
- # to resolve the dependencies.
- rev_map = map_.copy()
- self._map_branch_labels(
- has_branch_labels, cast(_RevisionMapType, map_)
- )
-
- # resolve dependency names from branch labels and symbolic
- # names
- self._add_depends_on(all_revisions, cast(_RevisionMapType, map_))
-
- for rev in map_.values():
- for downrev in rev._all_down_revisions:
- if downrev not in map_:
- util.warn(
- "Revision %s referenced from %s is not present"
- % (downrev, rev)
- )
- down_revision = map_[downrev]
- down_revision.add_nextrev(rev)
- if downrev in rev._versioned_down_revisions:
- heads.discard(down_revision)
- _real_heads.discard(down_revision)
-
- # once the map has downrevisions populated, the dependencies
- # can be further refined to include only those which are not
- # already ancestors
- self._normalize_depends_on(all_revisions, cast(_RevisionMapType, map_))
- self._detect_cycles(rev_map, heads, bases, _real_heads, _real_bases)
-
- revision_map: _RevisionMapType = dict(map_.items())
- revision_map[None] = revision_map[()] = None
- self.heads = tuple(rev.revision for rev in heads)
- self._real_heads = tuple(rev.revision for rev in _real_heads)
- self.bases = tuple(rev.revision for rev in bases)
- self._real_bases = tuple(rev.revision for rev in _real_bases)
-
- self._add_branches(has_branch_labels, revision_map)
- return revision_map
-
- def _detect_cycles(
- self,
- rev_map: _InterimRevisionMapType,
- heads: Set[Revision],
- bases: Tuple[Revision, ...],
- _real_heads: Set[Revision],
- _real_bases: Tuple[Revision, ...],
- ) -> None:
- if not rev_map:
- return
- if not heads or not bases:
- raise CycleDetected(list(rev_map))
- total_space = {
- rev.revision
- for rev in self._iterate_related_revisions(
- lambda r: r._versioned_down_revisions,
- heads,
- map_=cast(_RevisionMapType, rev_map),
- )
- }.intersection(
- rev.revision
- for rev in self._iterate_related_revisions(
- lambda r: r.nextrev,
- bases,
- map_=cast(_RevisionMapType, rev_map),
- )
- )
- deleted_revs = set(rev_map.keys()) - total_space
- if deleted_revs:
- raise CycleDetected(sorted(deleted_revs))
-
- if not _real_heads or not _real_bases:
- raise DependencyCycleDetected(list(rev_map))
- total_space = {
- rev.revision
- for rev in self._iterate_related_revisions(
- lambda r: r._all_down_revisions,
- _real_heads,
- map_=cast(_RevisionMapType, rev_map),
- )
- }.intersection(
- rev.revision
- for rev in self._iterate_related_revisions(
- lambda r: r._all_nextrev,
- _real_bases,
- map_=cast(_RevisionMapType, rev_map),
- )
- )
- deleted_revs = set(rev_map.keys()) - total_space
- if deleted_revs:
- raise DependencyCycleDetected(sorted(deleted_revs))
-
- def _map_branch_labels(
- self, revisions: Collection[Revision], map_: _RevisionMapType
- ) -> None:
- for revision in revisions:
- if revision.branch_labels:
- assert revision._orig_branch_labels is not None
- for branch_label in revision._orig_branch_labels:
- if branch_label in map_:
- map_rev = map_[branch_label]
- assert map_rev is not None
- raise RevisionError(
- "Branch name '%s' in revision %s already "
- "used by revision %s"
- % (
- branch_label,
- revision.revision,
- map_rev.revision,
- )
- )
- map_[branch_label] = revision
-
- def _add_branches(
- self, revisions: Collection[Revision], map_: _RevisionMapType
- ) -> None:
- for revision in revisions:
- if revision.branch_labels:
- revision.branch_labels.update(revision.branch_labels)
- for node in self._get_descendant_nodes(
- [revision], map_, include_dependencies=False
- ):
- node.branch_labels.update(revision.branch_labels)
-
- parent = node
- while (
- parent
- and not parent._is_real_branch_point
- and not parent.is_merge_point
- ):
- parent.branch_labels.update(revision.branch_labels)
- if parent.down_revision:
- parent = map_[parent.down_revision]
- else:
- break
-
- def _add_depends_on(
- self, revisions: Collection[Revision], map_: _RevisionMapType
- ) -> None:
- """Resolve the 'dependencies' for each revision in a collection
- in terms of actual revision ids, as opposed to branch labels or other
- symbolic names.
-
- The collection is then assigned to the _resolved_dependencies
- attribute on each revision object.
-
- """
-
- for revision in revisions:
- if revision.dependencies:
- deps = [
- map_[dep] for dep in util.to_tuple(revision.dependencies)
- ]
- revision._resolved_dependencies = tuple(
- [d.revision for d in deps if d is not None]
- )
- else:
- revision._resolved_dependencies = ()
-
- def _normalize_depends_on(
- self, revisions: Collection[Revision], map_: _RevisionMapType
- ) -> None:
- """Create a collection of "dependencies" that omits dependencies
- that are already ancestor nodes for each revision in a given
- collection.
-
- This builds upon the _resolved_dependencies collection created in the
- _add_depends_on() method, looking in the fully populated revision map
- for ancestors, and omitting them as the _resolved_dependencies
- collection as it is copied to a new collection. The new collection is
- then assigned to the _normalized_resolved_dependencies attribute on
- each revision object.
-
- The collection is then used to determine the immediate "down revision"
- identifiers for this revision.
-
- """
-
- for revision in revisions:
- if revision._resolved_dependencies:
- normalized_resolved = set(revision._resolved_dependencies)
- for rev in self._get_ancestor_nodes(
- [revision],
- include_dependencies=False,
- map_=map_,
- ):
- if rev is revision:
- continue
- elif rev._resolved_dependencies:
- normalized_resolved.difference_update(
- rev._resolved_dependencies
- )
-
- revision._normalized_resolved_dependencies = tuple(
- normalized_resolved
- )
- else:
- revision._normalized_resolved_dependencies = ()
-
- def add_revision(self, revision: Revision, _replace: bool = False) -> None:
- """add a single revision to an existing map.
-
- This method is for single-revision use cases, it's not
- appropriate for fully populating an entire revision map.
-
- """
- map_ = self._revision_map
- if not _replace and revision.revision in map_:
- util.warn(
- "Revision %s is present more than once" % revision.revision
- )
- elif _replace and revision.revision not in map_:
- raise Exception("revision %s not in map" % revision.revision)
-
- map_[revision.revision] = revision
-
- revisions = [revision]
- self._add_branches(revisions, map_)
- self._map_branch_labels(revisions, map_)
- self._add_depends_on(revisions, map_)
-
- if revision.is_base:
- self.bases += (revision.revision,)
- if revision._is_real_base:
- self._real_bases += (revision.revision,)
-
- for downrev in revision._all_down_revisions:
- if downrev not in map_:
- util.warn(
- "Revision %s referenced from %s is not present"
- % (downrev, revision)
- )
- not_none(map_[downrev]).add_nextrev(revision)
-
- self._normalize_depends_on(revisions, map_)
-
- if revision._is_real_head:
- self._real_heads = tuple(
- head
- for head in self._real_heads
- if head
- not in set(revision._all_down_revisions).union(
- [revision.revision]
- )
- ) + (revision.revision,)
- if revision.is_head:
- self.heads = tuple(
- head
- for head in self.heads
- if head
- not in set(revision._versioned_down_revisions).union(
- [revision.revision]
- )
- ) + (revision.revision,)
-
- def get_current_head(
- self, branch_label: Optional[str] = None
- ) -> Optional[str]:
- """Return the current head revision.
-
- If the script directory has multiple heads
- due to branching, an error is raised;
- :meth:`.ScriptDirectory.get_heads` should be
- preferred.
-
- :param branch_label: optional branch name which will limit the
- heads considered to those which include that branch_label.
-
- :return: a string revision number.
-
- .. seealso::
-
- :meth:`.ScriptDirectory.get_heads`
-
- """
- current_heads: Sequence[str] = self.heads
- if branch_label:
- current_heads = self.filter_for_lineage(
- current_heads, branch_label
- )
- if len(current_heads) > 1:
- raise MultipleHeads(
- current_heads,
- "%s@head" % branch_label if branch_label else "head",
- )
-
- if current_heads:
- return current_heads[0]
- else:
- return None
-
- def _get_base_revisions(self, identifier: str) -> Tuple[str, ...]:
- return self.filter_for_lineage(self.bases, identifier)
-
- def get_revisions(
- self, id_: Optional[_GetRevArg]
- ) -> Tuple[Optional[_RevisionOrBase], ...]:
- """Return the :class:`.Revision` instances with the given rev id
- or identifiers.
-
- May be given a single identifier, a sequence of identifiers, or the
- special symbols "head" or "base". The result is a tuple of one
- or more identifiers, or an empty tuple in the case of "base".
-
- In the cases where 'head', 'heads' is requested and the
- revision map is empty, returns an empty tuple.
-
- Supports partial identifiers, where the given identifier
- is matched against all identifiers that start with the given
- characters; if there is exactly one match, that determines the
- full revision.
-
- """
-
- if isinstance(id_, (list, tuple, set, frozenset)):
- return sum([self.get_revisions(id_elem) for id_elem in id_], ())
- else:
- resolved_id, branch_label = self._resolve_revision_number(id_)
- if len(resolved_id) == 1:
- try:
- rint = int(resolved_id[0])
- if rint < 0:
- # branch@-n -> walk down from heads
- select_heads = self.get_revisions("heads")
- if branch_label is not None:
- select_heads = tuple(
- head
- for head in select_heads
- if branch_label
- in is_revision(head).branch_labels
- )
- return tuple(
- self._walk(head, steps=rint)
- for head in select_heads
- )
- except ValueError:
- # couldn't resolve as integer
- pass
- return tuple(
- self._revision_for_ident(rev_id, branch_label)
- for rev_id in resolved_id
- )
-
- def get_revision(self, id_: Optional[str]) -> Optional[Revision]:
- """Return the :class:`.Revision` instance with the given rev id.
-
- If a symbolic name such as "head" or "base" is given, resolves
- the identifier into the current head or base revision. If the symbolic
- name refers to multiples, :class:`.MultipleHeads` is raised.
-
- Supports partial identifiers, where the given identifier
- is matched against all identifiers that start with the given
- characters; if there is exactly one match, that determines the
- full revision.
-
- """
-
- resolved_id, branch_label = self._resolve_revision_number(id_)
- if len(resolved_id) > 1:
- raise MultipleHeads(resolved_id, id_)
-
- resolved: Union[str, Tuple[()]] = resolved_id[0] if resolved_id else ()
- return self._revision_for_ident(resolved, branch_label)
-
- def _resolve_branch(self, branch_label: str) -> Optional[Revision]:
- try:
- branch_rev = self._revision_map[branch_label]
- except KeyError:
- try:
- nonbranch_rev = self._revision_for_ident(branch_label)
- except ResolutionError as re:
- raise ResolutionError(
- "No such branch: '%s'" % branch_label, branch_label
- ) from re
-
- else:
- return nonbranch_rev
- else:
- return branch_rev
-
- def _revision_for_ident(
- self,
- resolved_id: Union[str, Tuple[()], None],
- check_branch: Optional[str] = None,
- ) -> Optional[Revision]:
- branch_rev: Optional[Revision]
- if check_branch:
- branch_rev = self._resolve_branch(check_branch)
- else:
- branch_rev = None
-
- revision: Union[Optional[Revision], Literal[False]]
- try:
- revision = self._revision_map[resolved_id]
- except KeyError:
- # break out to avoid misleading py3k stack traces
- revision = False
- revs: Sequence[str]
- if revision is False:
- assert resolved_id
- # do a partial lookup
- revs = [
- x
- for x in self._revision_map
- if x and len(x) > 3 and x.startswith(resolved_id)
- ]
-
- if branch_rev:
- revs = self.filter_for_lineage(revs, check_branch)
- if not revs:
- raise ResolutionError(
- "No such revision or branch '%s'%s"
- % (
- resolved_id,
- (
- "; please ensure at least four characters are "
- "present for partial revision identifier matches"
- if len(resolved_id) < 4
- else ""
- ),
- ),
- resolved_id,
- )
- elif len(revs) > 1:
- raise ResolutionError(
- "Multiple revisions start "
- "with '%s': %s..."
- % (resolved_id, ", ".join("'%s'" % r for r in revs[0:3])),
- resolved_id,
- )
- else:
- revision = self._revision_map[revs[0]]
-
- if check_branch and revision is not None:
- assert branch_rev is not None
- assert resolved_id
- if not self._shares_lineage(
- revision.revision, branch_rev.revision
- ):
- raise ResolutionError(
- "Revision %s is not a member of branch '%s'"
- % (revision.revision, check_branch),
- resolved_id,
- )
- return revision
-
- def _filter_into_branch_heads(
- self, targets: Iterable[Optional[_RevisionOrBase]]
- ) -> Set[Optional[_RevisionOrBase]]:
- targets = set(targets)
-
- for rev in list(targets):
- assert rev
- if targets.intersection(
- self._get_descendant_nodes([rev], include_dependencies=False)
- ).difference([rev]):
- targets.discard(rev)
- return targets
-
- def filter_for_lineage(
- self,
- targets: Iterable[_TR],
- check_against: Optional[str],
- include_dependencies: bool = False,
- ) -> Tuple[_TR, ...]:
- id_, branch_label = self._resolve_revision_number(check_against)
-
- shares = []
- if branch_label:
- shares.append(branch_label)
- if id_:
- shares.extend(id_)
-
- return tuple(
- tg
- for tg in targets
- if self._shares_lineage(
- tg, shares, include_dependencies=include_dependencies
- )
- )
-
- def _shares_lineage(
- self,
- target: Optional[_RevisionOrStr],
- test_against_revs: Sequence[_RevisionOrStr],
- include_dependencies: bool = False,
- ) -> bool:
- if not test_against_revs:
- return True
- if not isinstance(target, Revision):
- resolved_target = not_none(self._revision_for_ident(target))
- else:
- resolved_target = target
-
- resolved_test_against_revs = [
- (
- self._revision_for_ident(test_against_rev)
- if not isinstance(test_against_rev, Revision)
- else test_against_rev
- )
- for test_against_rev in util.to_tuple(
- test_against_revs, default=()
- )
- ]
-
- return bool(
- set(
- self._get_descendant_nodes(
- [resolved_target],
- include_dependencies=include_dependencies,
- )
- )
- .union(
- self._get_ancestor_nodes(
- [resolved_target],
- include_dependencies=include_dependencies,
- )
- )
- .intersection(resolved_test_against_revs)
- )
-
- def _resolve_revision_number(
- self, id_: Optional[_GetRevArg]
- ) -> Tuple[Tuple[str, ...], Optional[str]]:
- branch_label: Optional[str]
- if isinstance(id_, str) and "@" in id_:
- branch_label, id_ = id_.split("@", 1)
-
- elif id_ is not None and (
- (isinstance(id_, tuple) and id_ and not isinstance(id_[0], str))
- or not isinstance(id_, (str, tuple))
- ):
- raise RevisionError(
- "revision identifier %r is not a string; ensure database "
- "driver settings are correct" % (id_,)
- )
-
- else:
- branch_label = None
-
- # ensure map is loaded
- self._revision_map
- if id_ == "heads":
- if branch_label:
- return (
- self.filter_for_lineage(self.heads, branch_label),
- branch_label,
- )
- else:
- return self._real_heads, branch_label
- elif id_ == "head":
- current_head = self.get_current_head(branch_label)
- if current_head:
- return (current_head,), branch_label
- else:
- return (), branch_label
- elif id_ == "base" or id_ is None:
- return (), branch_label
- else:
- return util.to_tuple(id_, default=None), branch_label
-
- def iterate_revisions(
- self,
- upper: _RevisionIdentifierType,
- lower: _RevisionIdentifierType,
- implicit_base: bool = False,
- inclusive: bool = False,
- assert_relative_length: bool = True,
- select_for_downgrade: bool = False,
- ) -> Iterator[Revision]:
- """Iterate through script revisions, starting at the given
- upper revision identifier and ending at the lower.
-
- The traversal uses strictly the `down_revision`
- marker inside each migration script, so
- it is a requirement that upper >= lower,
- else you'll get nothing back.
-
- The iterator yields :class:`.Revision` objects.
-
- """
- fn: _CollectRevisionsProtocol
- if select_for_downgrade:
- fn = self._collect_downgrade_revisions
- else:
- fn = self._collect_upgrade_revisions
-
- revisions, heads = fn(
- upper,
- lower,
- inclusive=inclusive,
- implicit_base=implicit_base,
- assert_relative_length=assert_relative_length,
- )
-
- for node in self._topological_sort(revisions, heads):
- yield not_none(self.get_revision(node))
-
- def _get_descendant_nodes(
- self,
- targets: Collection[Optional[_RevisionOrBase]],
- map_: Optional[_RevisionMapType] = None,
- check: bool = False,
- omit_immediate_dependencies: bool = False,
- include_dependencies: bool = True,
- ) -> Iterator[Any]:
- if omit_immediate_dependencies:
-
- def fn(rev: Revision) -> Iterable[str]:
- if rev not in targets:
- return rev._all_nextrev
- else:
- return rev.nextrev
-
- elif include_dependencies:
-
- def fn(rev: Revision) -> Iterable[str]:
- return rev._all_nextrev
-
- else:
-
- def fn(rev: Revision) -> Iterable[str]:
- return rev.nextrev
-
- return self._iterate_related_revisions(
- fn, targets, map_=map_, check=check
- )
-
- def _get_ancestor_nodes(
- self,
- targets: Collection[Optional[_RevisionOrBase]],
- map_: Optional[_RevisionMapType] = None,
- check: bool = False,
- include_dependencies: bool = True,
- ) -> Iterator[Revision]:
- if include_dependencies:
-
- def fn(rev: Revision) -> Iterable[str]:
- return rev._normalized_down_revisions
-
- else:
-
- def fn(rev: Revision) -> Iterable[str]:
- return rev._versioned_down_revisions
-
- return self._iterate_related_revisions(
- fn, targets, map_=map_, check=check
- )
-
- def _iterate_related_revisions(
- self,
- fn: Callable[[Revision], Iterable[str]],
- targets: Collection[Optional[_RevisionOrBase]],
- map_: Optional[_RevisionMapType],
- check: bool = False,
- ) -> Iterator[Revision]:
- if map_ is None:
- map_ = self._revision_map
-
- seen = set()
- todo: Deque[Revision] = collections.deque()
- for target_for in targets:
- target = is_revision(target_for)
- todo.append(target)
- if check:
- per_target = set()
-
- while todo:
- rev = todo.pop()
- if check:
- per_target.add(rev)
-
- if rev in seen:
- continue
- seen.add(rev)
- # Check for map errors before collecting.
- for rev_id in fn(rev):
- next_rev = map_[rev_id]
- assert next_rev is not None
- if next_rev.revision != rev_id:
- raise RevisionError(
- "Dependency resolution failed; broken map"
- )
- todo.append(next_rev)
- yield rev
- if check:
- overlaps = per_target.intersection(targets).difference(
- [target]
- )
- if overlaps:
- raise RevisionError(
- "Requested revision %s overlaps with "
- "other requested revisions %s"
- % (
- target.revision,
- ", ".join(r.revision for r in overlaps),
- )
- )
-
- def _topological_sort(
- self,
- revisions: Collection[Revision],
- heads: Any,
- ) -> List[str]:
- """Yield revision ids of a collection of Revision objects in
- topological sorted order (i.e. revisions always come after their
- down_revisions and dependencies). Uses the order of keys in
- _revision_map to sort.
-
- """
-
- id_to_rev = self._revision_map
-
- def get_ancestors(rev_id: str) -> Set[str]:
- return {
- r.revision
- for r in self._get_ancestor_nodes([id_to_rev[rev_id]])
- }
-
- todo = {d.revision for d in revisions}
-
- # Use revision map (ordered dict) key order to pre-sort.
- inserted_order = list(self._revision_map)
-
- current_heads = list(
- sorted(
- {d.revision for d in heads if d.revision in todo},
- key=inserted_order.index,
- )
- )
- ancestors_by_idx = [get_ancestors(rev_id) for rev_id in current_heads]
-
- output = []
-
- current_candidate_idx = 0
- while current_heads:
- candidate = current_heads[current_candidate_idx]
-
- for check_head_index, ancestors in enumerate(ancestors_by_idx):
- # scan all the heads. see if we can continue walking
- # down the current branch indicated by current_candidate_idx.
- if (
- check_head_index != current_candidate_idx
- and candidate in ancestors
- ):
- current_candidate_idx = check_head_index
- # nope, another head is dependent on us, they have
- # to be traversed first
- break
- else:
- # yup, we can emit
- if candidate in todo:
- output.append(candidate)
- todo.remove(candidate)
-
- # now update the heads with our ancestors.
-
- candidate_rev = id_to_rev[candidate]
- assert candidate_rev is not None
-
- heads_to_add = [
- r
- for r in candidate_rev._normalized_down_revisions
- if r in todo and r not in current_heads
- ]
-
- if not heads_to_add:
- # no ancestors, so remove this head from the list
- del current_heads[current_candidate_idx]
- del ancestors_by_idx[current_candidate_idx]
- current_candidate_idx = max(current_candidate_idx - 1, 0)
- else:
- if (
- not candidate_rev._normalized_resolved_dependencies
- and len(candidate_rev._versioned_down_revisions) == 1
- ):
- current_heads[current_candidate_idx] = heads_to_add[0]
-
- # for plain movement down a revision line without
- # any mergepoints, branchpoints, or deps, we
- # can update the ancestors collection directly
- # by popping out the candidate we just emitted
- ancestors_by_idx[current_candidate_idx].discard(
- candidate
- )
-
- else:
- # otherwise recalculate it again, things get
- # complicated otherwise. This can possibly be
- # improved to not run the whole ancestor thing
- # each time but it was getting complicated
- current_heads[current_candidate_idx] = heads_to_add[0]
- current_heads.extend(heads_to_add[1:])
- ancestors_by_idx[current_candidate_idx] = (
- get_ancestors(heads_to_add[0])
- )
- ancestors_by_idx.extend(
- get_ancestors(head) for head in heads_to_add[1:]
- )
-
- assert not todo
- return output
-
- def _walk(
- self,
- start: Optional[Union[str, Revision]],
- steps: int,
- branch_label: Optional[str] = None,
- no_overwalk: bool = True,
- ) -> Optional[_RevisionOrBase]:
- """
- Walk the requested number of :steps up (steps > 0) or down (steps < 0)
- the revision tree.
-
- :branch_label is used to select branches only when walking up.
-
- If the walk goes past the boundaries of the tree and :no_overwalk is
- True, None is returned, otherwise the walk terminates early.
-
- A RevisionError is raised if there is no unambiguous revision to
- walk to.
- """
- initial: Optional[_RevisionOrBase]
- if isinstance(start, str):
- initial = self.get_revision(start)
- else:
- initial = start
-
- children: Sequence[Optional[_RevisionOrBase]]
- for _ in range(abs(steps)):
- if steps > 0:
- assert initial != "base" # type: ignore[comparison-overlap]
- # Walk up
- walk_up = [
- is_revision(rev)
- for rev in self.get_revisions(
- self.bases if initial is None else initial.nextrev
- )
- ]
- if branch_label:
- children = self.filter_for_lineage(walk_up, branch_label)
- else:
- children = walk_up
- else:
- # Walk down
- if initial == "base": # type: ignore[comparison-overlap]
- children = ()
- else:
- children = self.get_revisions(
- self.heads
- if initial is None
- else initial.down_revision
- )
- if not children:
- children = ("base",)
- if not children:
- # This will return an invalid result if no_overwalk, otherwise
- # further steps will stay where we are.
- ret = None if no_overwalk else initial
- return ret
- elif len(children) > 1:
- raise RevisionError("Ambiguous walk")
- initial = children[0]
-
- return initial
-
- def _parse_downgrade_target(
- self,
- current_revisions: _RevisionIdentifierType,
- target: _RevisionIdentifierType,
- assert_relative_length: bool,
- ) -> Tuple[Optional[str], Optional[_RevisionOrBase]]:
- """
- Parse downgrade command syntax :target to retrieve the target revision
- and branch label (if any) given the :current_revisions stamp of the
- database.
-
- Returns a tuple (branch_label, target_revision) where branch_label
- is a string from the command specifying the branch to consider (or
- None if no branch given), and target_revision is a Revision object
- which the command refers to. target_revisions is None if the command
- refers to 'base'. The target may be specified in absolute form, or
- relative to :current_revisions.
- """
- if target is None:
- return None, None
- assert isinstance(
- target, str
- ), "Expected downgrade target in string form"
- match = _relative_destination.match(target)
- if match:
- branch_label, symbol, relative = match.groups()
- rel_int = int(relative)
- if rel_int >= 0:
- if symbol is None:
- # Downgrading to current + n is not valid.
- raise RevisionError(
- "Relative revision %s didn't "
- "produce %d migrations" % (relative, abs(rel_int))
- )
- # Find target revision relative to given symbol.
- rev = self._walk(
- symbol,
- rel_int,
- branch_label,
- no_overwalk=assert_relative_length,
- )
- if rev is None:
- raise RevisionError("Walked too far")
- return branch_label, rev
- else:
- relative_revision = symbol is None
- if relative_revision:
- # Find target revision relative to current state.
- if branch_label:
- cr_tuple = util.to_tuple(current_revisions)
- symbol_list: Sequence[str]
- symbol_list = self.filter_for_lineage(
- cr_tuple, branch_label
- )
- if not symbol_list:
- # check the case where there are multiple branches
- # but there is currently a single heads, since all
- # other branch heads are dependent of the current
- # single heads.
- all_current = cast(
- Set[Revision], self._get_all_current(cr_tuple)
- )
- sl_all_current = self.filter_for_lineage(
- all_current, branch_label
- )
- symbol_list = [
- r.revision if r else r # type: ignore[misc]
- for r in sl_all_current
- ]
-
- assert len(symbol_list) == 1
- symbol = symbol_list[0]
- else:
- current_revisions = util.to_tuple(current_revisions)
- if not current_revisions:
- raise RevisionError(
- "Relative revision %s didn't "
- "produce %d migrations"
- % (relative, abs(rel_int))
- )
- # Have to check uniques here for duplicate rows test.
- if len(set(current_revisions)) > 1:
- util.warn(
- "downgrade -1 from multiple heads is "
- "ambiguous; "
- "this usage will be disallowed in a future "
- "release."
- )
- symbol = current_revisions[0]
- # Restrict iteration to just the selected branch when
- # ambiguous branches are involved.
- branch_label = symbol
- # Walk down the tree to find downgrade target.
- rev = self._walk(
- start=(
- self.get_revision(symbol)
- if branch_label is None
- else self.get_revision(
- "%s@%s" % (branch_label, symbol)
- )
- ),
- steps=rel_int,
- no_overwalk=assert_relative_length,
- )
- if rev is None:
- if relative_revision:
- raise RevisionError(
- "Relative revision %s didn't "
- "produce %d migrations" % (relative, abs(rel_int))
- )
- else:
- raise RevisionError("Walked too far")
- return branch_label, rev
-
- # No relative destination given, revision specified is absolute.
- branch_label, _, symbol = target.rpartition("@")
- if not branch_label:
- branch_label = None
- return branch_label, self.get_revision(symbol)
-
- def _parse_upgrade_target(
- self,
- current_revisions: _RevisionIdentifierType,
- target: _RevisionIdentifierType,
- assert_relative_length: bool,
- ) -> Tuple[Optional[_RevisionOrBase], ...]:
- """
- Parse upgrade command syntax :target to retrieve the target revision
- and given the :current_revisions stamp of the database.
-
- Returns a tuple of Revision objects which should be iterated/upgraded
- to. The target may be specified in absolute form, or relative to
- :current_revisions.
- """
- if isinstance(target, str):
- match = _relative_destination.match(target)
- else:
- match = None
-
- if not match:
- # No relative destination, target is absolute.
- return self.get_revisions(target)
-
- current_revisions_tup: Union[str, Tuple[Optional[str], ...], None]
- current_revisions_tup = util.to_tuple(current_revisions)
-
- branch_label, symbol, relative_str = match.groups()
- relative = int(relative_str)
- if relative > 0:
- if symbol is None:
- if not current_revisions_tup:
- current_revisions_tup = (None,)
- # Try to filter to a single target (avoid ambiguous branches).
- start_revs = current_revisions_tup
- if branch_label:
- start_revs = self.filter_for_lineage(
- self.get_revisions(current_revisions_tup), # type: ignore[arg-type] # noqa: E501
- branch_label,
- )
- if not start_revs:
- # The requested branch is not a head, so we need to
- # backtrack to find a branchpoint.
- active_on_branch = self.filter_for_lineage(
- self._get_ancestor_nodes(
- self.get_revisions(current_revisions_tup)
- ),
- branch_label,
- )
- # Find the tips of this set of revisions (revisions
- # without children within the set).
- start_revs = tuple(
- {rev.revision for rev in active_on_branch}
- - {
- down
- for rev in active_on_branch
- for down in rev._normalized_down_revisions
- }
- )
- if not start_revs:
- # We must need to go right back to base to find
- # a starting point for this branch.
- start_revs = (None,)
- if len(start_revs) > 1:
- raise RevisionError(
- "Ambiguous upgrade from multiple current revisions"
- )
- # Walk up from unique target revision.
- rev = self._walk(
- start=start_revs[0],
- steps=relative,
- branch_label=branch_label,
- no_overwalk=assert_relative_length,
- )
- if rev is None:
- raise RevisionError(
- "Relative revision %s didn't "
- "produce %d migrations" % (relative_str, abs(relative))
- )
- return (rev,)
- else:
- # Walk is relative to a given revision, not the current state.
- return (
- self._walk(
- start=self.get_revision(symbol),
- steps=relative,
- branch_label=branch_label,
- no_overwalk=assert_relative_length,
- ),
- )
- else:
- if symbol is None:
- # Upgrading to current - n is not valid.
- raise RevisionError(
- "Relative revision %s didn't "
- "produce %d migrations" % (relative, abs(relative))
- )
- return (
- self._walk(
- start=(
- self.get_revision(symbol)
- if branch_label is None
- else self.get_revision(
- "%s@%s" % (branch_label, symbol)
- )
- ),
- steps=relative,
- no_overwalk=assert_relative_length,
- ),
- )
-
- def _collect_downgrade_revisions(
- self,
- upper: _RevisionIdentifierType,
- lower: _RevisionIdentifierType,
- inclusive: bool,
- implicit_base: bool,
- assert_relative_length: bool,
- ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]:
- """
- Compute the set of current revisions specified by :upper, and the
- downgrade target specified by :target. Return all dependents of target
- which are currently active.
-
- :inclusive=True includes the target revision in the set
- """
-
- branch_label, target_revision = self._parse_downgrade_target(
- current_revisions=upper,
- target=lower,
- assert_relative_length=assert_relative_length,
- )
- if target_revision == "base":
- target_revision = None
- assert target_revision is None or isinstance(target_revision, Revision)
-
- roots: List[Revision]
- # Find candidates to drop.
- if target_revision is None:
- # Downgrading back to base: find all tree roots.
- roots = [
- rev
- for rev in self._revision_map.values()
- if rev is not None and rev.down_revision is None
- ]
- elif inclusive:
- # inclusive implies target revision should also be dropped
- roots = [target_revision]
- else:
- # Downgrading to fixed target: find all direct children.
- roots = [
- is_revision(rev)
- for rev in self.get_revisions(target_revision.nextrev)
- ]
-
- if branch_label and len(roots) > 1:
- # Need to filter roots.
- ancestors = {
- rev.revision
- for rev in self._get_ancestor_nodes(
- [self._resolve_branch(branch_label)],
- include_dependencies=False,
- )
- }
- # Intersection gives the root revisions we are trying to
- # rollback with the downgrade.
- roots = [
- is_revision(rev)
- for rev in self.get_revisions(
- {rev.revision for rev in roots}.intersection(ancestors)
- )
- ]
-
- # Ensure we didn't throw everything away when filtering branches.
- if len(roots) == 0:
- raise RevisionError(
- "Not a valid downgrade target from current heads"
- )
-
- heads = self.get_revisions(upper)
-
- # Aim is to drop :branch_revision; to do so we also need to drop its
- # descendents and anything dependent on it.
- downgrade_revisions = set(
- self._get_descendant_nodes(
- roots,
- include_dependencies=True,
- omit_immediate_dependencies=False,
- )
- )
- active_revisions = set(
- self._get_ancestor_nodes(heads, include_dependencies=True)
- )
-
- # Emit revisions to drop in reverse topological sorted order.
- downgrade_revisions.intersection_update(active_revisions)
-
- if implicit_base:
- # Wind other branches back to base.
- downgrade_revisions.update(
- active_revisions.difference(self._get_ancestor_nodes(roots))
- )
-
- if (
- target_revision is not None
- and not downgrade_revisions
- and target_revision not in heads
- ):
- # Empty intersection: target revs are not present.
-
- raise RangeNotAncestorError("Nothing to drop", upper)
-
- return downgrade_revisions, heads
-
- def _collect_upgrade_revisions(
- self,
- upper: _RevisionIdentifierType,
- lower: _RevisionIdentifierType,
- inclusive: bool,
- implicit_base: bool,
- assert_relative_length: bool,
- ) -> Tuple[Set[Revision], Tuple[Revision, ...]]:
- """
- Compute the set of required revisions specified by :upper, and the
- current set of active revisions specified by :lower. Find the
- difference between the two to compute the required upgrades.
-
- :inclusive=True includes the current/lower revisions in the set
-
- :implicit_base=False only returns revisions which are downstream
- of the current/lower revisions. Dependencies from branches with
- different bases will not be included.
- """
- targets: Collection[Revision] = [
- is_revision(rev)
- for rev in self._parse_upgrade_target(
- current_revisions=lower,
- target=upper,
- assert_relative_length=assert_relative_length,
- )
- ]
-
- # assert type(targets) is tuple, "targets should be a tuple"
-
- # Handled named bases (e.g. branch@... -> heads should only produce
- # targets on the given branch)
- if isinstance(lower, str) and "@" in lower:
- branch, _, _ = lower.partition("@")
- branch_rev = self.get_revision(branch)
- if branch_rev is not None and branch_rev.revision == branch:
- # A revision was used as a label; get its branch instead
- assert len(branch_rev.branch_labels) == 1
- branch = next(iter(branch_rev.branch_labels))
- targets = {
- need for need in targets if branch in need.branch_labels
- }
-
- required_node_set = set(
- self._get_ancestor_nodes(
- targets, check=True, include_dependencies=True
- )
- ).union(targets)
-
- current_revisions = self.get_revisions(lower)
- if not implicit_base and any(
- rev not in required_node_set
- for rev in current_revisions
- if rev is not None
- ):
- raise RangeNotAncestorError(lower, upper)
- assert (
- type(current_revisions) is tuple
- ), "current_revisions should be a tuple"
-
- # Special case where lower = a relative value (get_revisions can't
- # find it)
- if current_revisions and current_revisions[0] is None:
- _, rev = self._parse_downgrade_target(
- current_revisions=upper,
- target=lower,
- assert_relative_length=assert_relative_length,
- )
- assert rev
- if rev == "base":
- current_revisions = tuple()
- lower = None
- else:
- current_revisions = (rev,)
- lower = rev.revision
-
- current_node_set = set(
- self._get_ancestor_nodes(
- current_revisions, check=True, include_dependencies=True
- )
- ).union(current_revisions)
-
- needs = required_node_set.difference(current_node_set)
-
- # Include the lower revision (=current_revisions?) in the iteration
- if inclusive:
- needs.update(is_revision(rev) for rev in self.get_revisions(lower))
- # By default, base is implicit as we want all dependencies returned.
- # Base is also implicit if lower = base
- # implicit_base=False -> only return direct downstreams of
- # current_revisions
- if current_revisions and not implicit_base:
- lower_descendents = self._get_descendant_nodes(
- [is_revision(rev) for rev in current_revisions],
- check=True,
- include_dependencies=False,
- )
- needs.intersection_update(lower_descendents)
-
- return needs, tuple(targets)
-
- def _get_all_current(
- self, id_: Tuple[str, ...]
- ) -> Set[Optional[_RevisionOrBase]]:
- top_revs: Set[Optional[_RevisionOrBase]]
- top_revs = set(self.get_revisions(id_))
- top_revs.update(
- self._get_ancestor_nodes(list(top_revs), include_dependencies=True)
- )
- return self._filter_into_branch_heads(top_revs)
-
-
-class Revision:
- """Base class for revisioned objects.
-
- The :class:`.Revision` class is the base of the more public-facing
- :class:`.Script` object, which represents a migration script.
- The mechanics of revision management and traversal are encapsulated
- within :class:`.Revision`, while :class:`.Script` applies this logic
- to Python files in a version directory.
-
- """
-
- nextrev: FrozenSet[str] = frozenset()
- """following revisions, based on down_revision only."""
-
- _all_nextrev: FrozenSet[str] = frozenset()
-
- revision: str = None # type: ignore[assignment]
- """The string revision number."""
-
- down_revision: Optional[_RevIdType] = None
- """The ``down_revision`` identifier(s) within the migration script.
-
- Note that the total set of "down" revisions is
- down_revision + dependencies.
-
- """
-
- dependencies: Optional[_RevIdType] = None
- """Additional revisions which this revision is dependent on.
-
- From a migration standpoint, these dependencies are added to the
- down_revision to form the full iteration. However, the separation
- of down_revision from "dependencies" is to assist in navigating
- a history that contains many branches, typically a multi-root scenario.
-
- """
-
- branch_labels: Set[str] = None # type: ignore[assignment]
- """Optional string/tuple of symbolic names to apply to this
- revision's branch"""
-
- _resolved_dependencies: Tuple[str, ...]
- _normalized_resolved_dependencies: Tuple[str, ...]
-
- @classmethod
- def verify_rev_id(cls, revision: str) -> None:
- illegal_chars = set(revision).intersection(_revision_illegal_chars)
- if illegal_chars:
- raise RevisionError(
- "Character(s) '%s' not allowed in revision identifier '%s'"
- % (", ".join(sorted(illegal_chars)), revision)
- )
-
- def __init__(
- self,
- revision: str,
- down_revision: Optional[Union[str, Tuple[str, ...]]],
- dependencies: Optional[Union[str, Tuple[str, ...]]] = None,
- branch_labels: Optional[Union[str, Tuple[str, ...]]] = None,
- ) -> None:
- if down_revision and revision in util.to_tuple(down_revision):
- raise LoopDetected(revision)
- elif dependencies is not None and revision in util.to_tuple(
- dependencies
- ):
- raise DependencyLoopDetected(revision)
-
- self.verify_rev_id(revision)
- self.revision = revision
- self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision))
- self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies))
- self._orig_branch_labels = util.to_tuple(branch_labels, default=())
- self.branch_labels = set(self._orig_branch_labels)
-
- def __repr__(self) -> str:
- args = [repr(self.revision), repr(self.down_revision)]
- if self.dependencies:
- args.append("dependencies=%r" % (self.dependencies,))
- if self.branch_labels:
- args.append("branch_labels=%r" % (self.branch_labels,))
- return "%s(%s)" % (self.__class__.__name__, ", ".join(args))
-
- def add_nextrev(self, revision: Revision) -> None:
- self._all_nextrev = self._all_nextrev.union([revision.revision])
- if self.revision in revision._versioned_down_revisions:
- self.nextrev = self.nextrev.union([revision.revision])
-
- @property
- def _all_down_revisions(self) -> Tuple[str, ...]:
- return util.dedupe_tuple(
- util.to_tuple(self.down_revision, default=())
- + self._resolved_dependencies
- )
-
- @property
- def _normalized_down_revisions(self) -> Tuple[str, ...]:
- """return immediate down revisions for a rev, omitting dependencies
- that are still dependencies of ancestors.
-
- """
- return util.dedupe_tuple(
- util.to_tuple(self.down_revision, default=())
- + self._normalized_resolved_dependencies
- )
-
- @property
- def _versioned_down_revisions(self) -> Tuple[str, ...]:
- return util.to_tuple(self.down_revision, default=())
-
- @property
- def is_head(self) -> bool:
- """Return True if this :class:`.Revision` is a 'head' revision.
-
- This is determined based on whether any other :class:`.Script`
- within the :class:`.ScriptDirectory` refers to this
- :class:`.Script`. Multiple heads can be present.
-
- """
- return not bool(self.nextrev)
-
- @property
- def _is_real_head(self) -> bool:
- return not bool(self._all_nextrev)
-
- @property
- def is_base(self) -> bool:
- """Return True if this :class:`.Revision` is a 'base' revision."""
-
- return self.down_revision is None
-
- @property
- def _is_real_base(self) -> bool:
- """Return True if this :class:`.Revision` is a "real" base revision,
- e.g. that it has no dependencies either."""
-
- # we use self.dependencies here because this is called up
- # in initialization where _real_dependencies isn't set up
- # yet
- return self.down_revision is None and self.dependencies is None
-
- @property
- def is_branch_point(self) -> bool:
- """Return True if this :class:`.Script` is a branch point.
-
- A branchpoint is defined as a :class:`.Script` which is referred
- to by more than one succeeding :class:`.Script`, that is more
- than one :class:`.Script` has a `down_revision` identifier pointing
- here.
-
- """
- return len(self.nextrev) > 1
-
- @property
- def _is_real_branch_point(self) -> bool:
- """Return True if this :class:`.Script` is a 'real' branch point,
- taking into account dependencies as well.
-
- """
- return len(self._all_nextrev) > 1
-
- @property
- def is_merge_point(self) -> bool:
- """Return True if this :class:`.Script` is a merge point."""
-
- return len(self._versioned_down_revisions) > 1
-
-
-@overload
-def tuple_rev_as_scalar(rev: None) -> None: ...
-
-
-@overload
-def tuple_rev_as_scalar(
- rev: Union[Tuple[_T, ...], List[_T]],
-) -> Union[_T, Tuple[_T, ...], List[_T]]: ...
-
-
-def tuple_rev_as_scalar(
- rev: Optional[Sequence[_T]],
-) -> Union[_T, Sequence[_T], None]:
- if not rev:
- return None
- elif len(rev) == 1:
- return rev[0]
- else:
- return rev
-
-
-def is_revision(rev: Any) -> Revision:
- assert isinstance(rev, Revision)
- return rev
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/script/write_hooks.py b/backend/.venv/lib/python3.12/site-packages/alembic/script/write_hooks.py
deleted file mode 100644
index 3dd49d9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/script/write_hooks.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import importlib.util
-import os
-import shlex
-import subprocess
-import sys
-from typing import Any
-from typing import Callable
-from typing import TYPE_CHECKING
-
-from .. import util
-from ..util import compat
-from ..util.pyfiles import _preserving_path_as_str
-
-if TYPE_CHECKING:
- from ..config import PostWriteHookConfig
-
-REVISION_SCRIPT_TOKEN = "REVISION_SCRIPT_FILENAME"
-
-_registry: dict = {}
-
-
-def register(name: str) -> Callable:
- """A function decorator that will register that function as a write hook.
-
- See the documentation linked below for an example.
-
- .. seealso::
-
- :ref:`post_write_hooks_custom`
-
-
- """
-
- def decorate(fn):
- _registry[name] = fn
- return fn
-
- return decorate
-
-
-def _invoke(
- name: str,
- revision_path: str | os.PathLike[str],
- options: PostWriteHookConfig,
-) -> Any:
- """Invokes the formatter registered for the given name.
-
- :param name: The name of a formatter in the registry
- :param revision: string path to the revision file
- :param options: A dict containing kwargs passed to the
- specified formatter.
- :raises: :class:`alembic.util.CommandError`
- """
- revision_path = _preserving_path_as_str(revision_path)
- try:
- hook = _registry[name]
- except KeyError as ke:
- raise util.CommandError(
- f"No formatter with name '{name}' registered"
- ) from ke
- else:
- return hook(revision_path, options)
-
-
-def _run_hooks(
- path: str | os.PathLike[str], hooks: list[PostWriteHookConfig]
-) -> None:
- """Invoke hooks for a generated revision."""
-
- for hook in hooks:
- name = hook["_hook_name"]
- try:
- type_ = hook["type"]
- except KeyError as ke:
- raise util.CommandError(
- f"Key '{name}.type' (or 'type' in toml) is required "
- f"for post write hook {name!r}"
- ) from ke
- else:
- with util.status(
- f"Running post write hook {name!r}", newline=True
- ):
- _invoke(type_, path, hook)
-
-
-def _parse_cmdline_options(cmdline_options_str: str, path: str) -> list[str]:
- """Parse options from a string into a list.
-
- Also substitutes the revision script token with the actual filename of
- the revision script.
-
- If the revision script token doesn't occur in the options string, it is
- automatically prepended.
- """
- if REVISION_SCRIPT_TOKEN not in cmdline_options_str:
- cmdline_options_str = REVISION_SCRIPT_TOKEN + " " + cmdline_options_str
- cmdline_options_list = shlex.split(
- cmdline_options_str, posix=compat.is_posix
- )
- cmdline_options_list = [
- option.replace(REVISION_SCRIPT_TOKEN, path)
- for option in cmdline_options_list
- ]
- return cmdline_options_list
-
-
-def _get_required_option(options: dict, name: str) -> str:
- try:
- return options[name]
- except KeyError as ke:
- raise util.CommandError(
- f"Key {options['_hook_name']}.{name} is required for post "
- f"write hook {options['_hook_name']!r}"
- ) from ke
-
-
-def _run_hook(
- path: str, options: dict, ignore_output: bool, command: list[str]
-) -> None:
- cwd: str | None = options.get("cwd", None)
- cmdline_options_str = options.get("options", "")
- cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path)
-
- kw: dict[str, Any] = {}
- if ignore_output:
- kw["stdout"] = kw["stderr"] = subprocess.DEVNULL
-
- subprocess.run([*command, *cmdline_options_list], cwd=cwd, **kw)
-
-
-@register("console_scripts")
-def console_scripts(
- path: str,
- options: dict,
- ignore_output: bool = False,
- verify_version: tuple[int, ...] | None = None,
-) -> None:
- entrypoint_name = _get_required_option(options, "entrypoint")
- for entry in compat.importlib_metadata_get("console_scripts"):
- if entry.name == entrypoint_name:
- impl: Any = entry
- break
- else:
- raise util.CommandError(
- f"Could not find entrypoint console_scripts.{entrypoint_name}"
- )
-
- if verify_version:
- pyscript = (
- f"import {impl.module}; "
- f"assert tuple(int(x) for x in {impl.module}.__version__.split('.')) >= {verify_version}, " # noqa: E501
- f"'need exactly version {verify_version} of {impl.name}'; "
- f"{impl.module}.{impl.attr}()"
- )
- else:
- pyscript = f"import {impl.module}; {impl.module}.{impl.attr}()"
-
- command = [sys.executable, "-c", pyscript]
- _run_hook(path, options, ignore_output, command)
-
-
-@register("exec")
-def exec_(path: str, options: dict, ignore_output: bool = False) -> None:
- executable = _get_required_option(options, "executable")
- _run_hook(path, options, ignore_output, command=[executable])
-
-
-@register("module")
-def module(path: str, options: dict, ignore_output: bool = False) -> None:
- module_name = _get_required_option(options, "module")
-
- if importlib.util.find_spec(module_name) is None:
- raise util.CommandError(f"Could not find module {module_name}")
-
- command = [sys.executable, "-m", module_name]
- _run_hook(path, options, ignore_output, command)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/README b/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/README
deleted file mode 100644
index e0d0858..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/README
+++ /dev/null
@@ -1 +0,0 @@
-Generic single-database configuration with an async dbapi.
\ No newline at end of file
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 17ae95a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/alembic.ini.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/alembic.ini.mako
deleted file mode 100644
index 02ccb0f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/alembic.ini.mako
+++ /dev/null
@@ -1,149 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-# path to migration scripts.
-# this is typically a path given in POSIX (e.g. forward slashes)
-# format, relative to the token %(here)s which refers to the location of this
-# ini file
-script_location = ${script_location}
-
-# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
-# Uncomment the line below if you want the files to be prepended with date and time
-# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
-# for all available tokens
-# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
-# Or organize into date-based subdirectories (requires recursive_version_locations = true)
-# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
-
-# sys.path path, will be prepended to sys.path if present.
-# defaults to the current working directory. for multiple paths, the path separator
-# is defined by "path_separator" below.
-prepend_sys_path = .
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the tzdata library which can be installed by adding
-# `alembic[tz]` to the pip requirements.
-# string value is passed to ZoneInfo()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to /versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# The path separator used here should be the separator specified by "path_separator"
-# below.
-# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
-
-# path_separator; This indicates what character is used to split lists of file
-# paths, including version_locations and prepend_sys_path within configparser
-# files such as alembic.ini.
-# The default rendered in new alembic.ini files is "os", which uses os.pathsep
-# to provide os-dependent path splitting.
-#
-# Note that in order to support legacy alembic.ini files, this default does NOT
-# take place if path_separator is not present in alembic.ini. If this
-# option is omitted entirely, fallback logic is as follows:
-#
-# 1. Parsing of the version_locations option falls back to using the legacy
-# "version_path_separator" key, which if absent then falls back to the legacy
-# behavior of splitting on spaces and/or commas.
-# 2. Parsing of the prepend_sys_path option falls back to the legacy
-# behavior of splitting on spaces, commas, or colons.
-#
-# Valid values for path_separator are:
-#
-# path_separator = :
-# path_separator = ;
-# path_separator = space
-# path_separator = newline
-#
-# Use os.pathsep. Default configuration used for new projects.
-path_separator = os
-
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# new in Alembic version 1.10
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = utf-8
-
-# database URL. This is consumed by the user-maintained env.py script only.
-# other means of configuring database URLs may be customized within the env.py
-# file.
-sqlalchemy.url = driver://user:pass@localhost/dbname
-
-
-[post_write_hooks]
-# post_write_hooks defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-
-# format using "black" - use the console_scripts runner, against the "black" entrypoint
-# hooks = black
-# black.type = console_scripts
-# black.entrypoint = black
-# black.options = -l 79 REVISION_SCRIPT_FILENAME
-
-# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
-# hooks = ruff
-# ruff.type = module
-# ruff.module = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Alternatively, use the exec runner to execute a binary found on your PATH
-# hooks = ruff
-# ruff.type = exec
-# ruff.executable = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Logging configuration. This is also consumed by the user-maintained
-# env.py script only.
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARNING
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/env.py
deleted file mode 100644
index 9f2d519..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/env.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import asyncio
-from logging.config import fileConfig
-
-from sqlalchemy import pool
-from sqlalchemy.engine import Connection
-from sqlalchemy.ext.asyncio import async_engine_from_config
-
-from alembic import context
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
- fileConfig(config.config_file_name)
-
-# add your model's MetaData object here
-# for 'autogenerate' support
-# from myapp import mymodel
-# target_metadata = mymodel.Base.metadata
-target_metadata = None
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def run_migrations_offline() -> None:
- """Run migrations in 'offline' mode.
-
- This configures the context with just a URL
- and not an Engine, though an Engine is acceptable
- here as well. By skipping the Engine creation
- we don't even need a DBAPI to be available.
-
- Calls to context.execute() here emit the given string to the
- script output.
-
- """
- url = config.get_main_option("sqlalchemy.url")
- context.configure(
- url=url,
- target_metadata=target_metadata,
- literal_binds=True,
- dialect_opts={"paramstyle": "named"},
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-def do_run_migrations(connection: Connection) -> None:
- context.configure(connection=connection, target_metadata=target_metadata)
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-async def run_async_migrations() -> None:
- """In this scenario we need to create an Engine
- and associate a connection with the context.
-
- """
-
- connectable = async_engine_from_config(
- config.get_section(config.config_ini_section, {}),
- prefix="sqlalchemy.",
- poolclass=pool.NullPool,
- )
-
- async with connectable.connect() as connection:
- await connection.run_sync(do_run_migrations)
-
- await connectable.dispose()
-
-
-def run_migrations_online() -> None:
- """Run migrations in 'online' mode."""
-
- asyncio.run(run_async_migrations())
-
-
-if context.is_offline_mode():
- run_migrations_offline()
-else:
- run_migrations_online()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako
deleted file mode 100644
index 1101630..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako
+++ /dev/null
@@ -1,28 +0,0 @@
-"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision: str = ${repr(up_revision)}
-down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
-branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
-depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
-
-
-def upgrade() -> None:
- """Upgrade schema."""
- ${upgrades if upgrades else "pass"}
-
-
-def downgrade() -> None:
- """Downgrade schema."""
- ${downgrades if downgrades else "pass"}
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/README b/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/README
deleted file mode 100644
index 98e4f9c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/README
+++ /dev/null
@@ -1 +0,0 @@
-Generic single-database configuration.
\ No newline at end of file
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 474428d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/alembic.ini.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/alembic.ini.mako
deleted file mode 100644
index 0127b2a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/alembic.ini.mako
+++ /dev/null
@@ -1,149 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-# path to migration scripts.
-# this is typically a path given in POSIX (e.g. forward slashes)
-# format, relative to the token %(here)s which refers to the location of this
-# ini file
-script_location = ${script_location}
-
-# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
-# Uncomment the line below if you want the files to be prepended with date and time
-# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
-# for all available tokens
-# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
-# Or organize into date-based subdirectories (requires recursive_version_locations = true)
-# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
-
-# sys.path path, will be prepended to sys.path if present.
-# defaults to the current working directory. for multiple paths, the path separator
-# is defined by "path_separator" below.
-prepend_sys_path = .
-
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the tzdata library which can be installed by adding
-# `alembic[tz]` to the pip requirements.
-# string value is passed to ZoneInfo()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to /versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# The path separator used here should be the separator specified by "path_separator"
-# below.
-# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
-
-# path_separator; This indicates what character is used to split lists of file
-# paths, including version_locations and prepend_sys_path within configparser
-# files such as alembic.ini.
-# The default rendered in new alembic.ini files is "os", which uses os.pathsep
-# to provide os-dependent path splitting.
-#
-# Note that in order to support legacy alembic.ini files, this default does NOT
-# take place if path_separator is not present in alembic.ini. If this
-# option is omitted entirely, fallback logic is as follows:
-#
-# 1. Parsing of the version_locations option falls back to using the legacy
-# "version_path_separator" key, which if absent then falls back to the legacy
-# behavior of splitting on spaces and/or commas.
-# 2. Parsing of the prepend_sys_path option falls back to the legacy
-# behavior of splitting on spaces, commas, or colons.
-#
-# Valid values for path_separator are:
-#
-# path_separator = :
-# path_separator = ;
-# path_separator = space
-# path_separator = newline
-#
-# Use os.pathsep. Default configuration used for new projects.
-path_separator = os
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# new in Alembic version 1.10
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = utf-8
-
-# database URL. This is consumed by the user-maintained env.py script only.
-# other means of configuring database URLs may be customized within the env.py
-# file.
-sqlalchemy.url = driver://user:pass@localhost/dbname
-
-
-[post_write_hooks]
-# post_write_hooks defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-
-# format using "black" - use the console_scripts runner, against the "black" entrypoint
-# hooks = black
-# black.type = console_scripts
-# black.entrypoint = black
-# black.options = -l 79 REVISION_SCRIPT_FILENAME
-
-# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
-# hooks = ruff
-# ruff.type = module
-# ruff.module = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Alternatively, use the exec runner to execute a binary found on your PATH
-# hooks = ruff
-# ruff.type = exec
-# ruff.executable = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Logging configuration. This is also consumed by the user-maintained
-# env.py script only.
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARNING
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/env.py
deleted file mode 100644
index 36112a3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/env.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from logging.config import fileConfig
-
-from sqlalchemy import engine_from_config
-from sqlalchemy import pool
-
-from alembic import context
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
- fileConfig(config.config_file_name)
-
-# add your model's MetaData object here
-# for 'autogenerate' support
-# from myapp import mymodel
-# target_metadata = mymodel.Base.metadata
-target_metadata = None
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def run_migrations_offline() -> None:
- """Run migrations in 'offline' mode.
-
- This configures the context with just a URL
- and not an Engine, though an Engine is acceptable
- here as well. By skipping the Engine creation
- we don't even need a DBAPI to be available.
-
- Calls to context.execute() here emit the given string to the
- script output.
-
- """
- url = config.get_main_option("sqlalchemy.url")
- context.configure(
- url=url,
- target_metadata=target_metadata,
- literal_binds=True,
- dialect_opts={"paramstyle": "named"},
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-def run_migrations_online() -> None:
- """Run migrations in 'online' mode.
-
- In this scenario we need to create an Engine
- and associate a connection with the context.
-
- """
- connectable = engine_from_config(
- config.get_section(config.config_ini_section, {}),
- prefix="sqlalchemy.",
- poolclass=pool.NullPool,
- )
-
- with connectable.connect() as connection:
- context.configure(
- connection=connection, target_metadata=target_metadata
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-if context.is_offline_mode():
- run_migrations_offline()
-else:
- run_migrations_online()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako
deleted file mode 100644
index 1101630..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako
+++ /dev/null
@@ -1,28 +0,0 @@
-"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision: str = ${repr(up_revision)}
-down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
-branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
-depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
-
-
-def upgrade() -> None:
- """Upgrade schema."""
- ${upgrades if upgrades else "pass"}
-
-
-def downgrade() -> None:
- """Downgrade schema."""
- ${downgrades if downgrades else "pass"}
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/README b/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/README
deleted file mode 100644
index f046ec9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/README
+++ /dev/null
@@ -1,12 +0,0 @@
-Rudimentary multi-database configuration.
-
-Multi-DB isn't vastly different from generic. The primary difference is that it
-will run the migrations N times (depending on how many databases you have
-configured), providing one engine name and associated context for each run.
-
-That engine name will then allow the migration to restrict what runs within it to
-just the appropriate migrations for that engine. You can see this behavior within
-the mako template.
-
-In the provided configuration, you'll need to have `databases` provided in
-alembic's config, and an `sqlalchemy.url` provided for each engine name.
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 46666b2..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako
deleted file mode 100644
index 7684646..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako
+++ /dev/null
@@ -1,157 +0,0 @@
-# a multi-database configuration.
-
-[alembic]
-# path to migration scripts.
-# this is typically a path given in POSIX (e.g. forward slashes)
-# format, relative to the token %(here)s which refers to the location of this
-# ini file
-script_location = ${script_location}
-
-# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
-# Uncomment the line below if you want the files to be prepended with date and time
-# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
-# for all available tokens
-# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
-# Or organize into date-based subdirectories (requires recursive_version_locations = true)
-# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
-
-# sys.path path, will be prepended to sys.path if present.
-# defaults to the current working directory. for multiple paths, the path separator
-# is defined by "path_separator" below.
-prepend_sys_path = .
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the tzdata library which can be installed by adding
-# `alembic[tz]` to the pip requirements.
-# string value is passed to ZoneInfo()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to /versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# The path separator used here should be the separator specified by "path_separator"
-# below.
-# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
-
-# path_separator; This indicates what character is used to split lists of file
-# paths, including version_locations and prepend_sys_path within configparser
-# files such as alembic.ini.
-# The default rendered in new alembic.ini files is "os", which uses os.pathsep
-# to provide os-dependent path splitting.
-#
-# Note that in order to support legacy alembic.ini files, this default does NOT
-# take place if path_separator is not present in alembic.ini. If this
-# option is omitted entirely, fallback logic is as follows:
-#
-# 1. Parsing of the version_locations option falls back to using the legacy
-# "version_path_separator" key, which if absent then falls back to the legacy
-# behavior of splitting on spaces and/or commas.
-# 2. Parsing of the prepend_sys_path option falls back to the legacy
-# behavior of splitting on spaces, commas, or colons.
-#
-# Valid values for path_separator are:
-#
-# path_separator = :
-# path_separator = ;
-# path_separator = space
-# path_separator = newline
-#
-# Use os.pathsep. Default configuration used for new projects.
-path_separator = os
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# new in Alembic version 1.10
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = utf-8
-
-# for multiple database configuration, new named sections are added
-# which each include a distinct ``sqlalchemy.url`` entry. A custom value
-# ``databases`` is added which indicates a listing of the per-database sections.
-# The ``databases`` entry as well as the URLs present in the ``[engine1]``
-# and ``[engine2]`` sections continue to be consumed by the user-maintained env.py
-# script only.
-
-databases = engine1, engine2
-
-[engine1]
-sqlalchemy.url = driver://user:pass@localhost/dbname
-
-[engine2]
-sqlalchemy.url = driver://user:pass@localhost/dbname2
-
-[post_write_hooks]
-# post_write_hooks defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-
-# format using "black" - use the console_scripts runner, against the "black" entrypoint
-# hooks = black
-# black.type = console_scripts
-# black.entrypoint = black
-# black.options = -l 79 REVISION_SCRIPT_FILENAME
-
-# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
-# hooks = ruff
-# ruff.type = module
-# ruff.module = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Alternatively, use the exec runner to execute a binary found on your PATH
-# hooks = ruff
-# ruff.type = exec
-# ruff.executable = ruff
-# ruff.options = check --fix REVISION_SCRIPT_FILENAME
-
-# Logging configuration. This is also consumed by the user-maintained
-# env.py script only.
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARNING
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py
deleted file mode 100644
index e937b64..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import logging
-from logging.config import fileConfig
-import re
-
-from sqlalchemy import engine_from_config
-from sqlalchemy import pool
-
-from alembic import context
-
-USE_TWOPHASE = False
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
- fileConfig(config.config_file_name)
-logger = logging.getLogger("alembic.env")
-
-# gather section names referring to different
-# databases. These are named "engine1", "engine2"
-# in the sample .ini file.
-db_names = config.get_main_option("databases", "")
-
-# add your model's MetaData objects here
-# for 'autogenerate' support. These must be set
-# up to hold just those tables targeting a
-# particular database. table.tometadata() may be
-# helpful here in case a "copy" of
-# a MetaData is needed.
-# from myapp import mymodel
-# target_metadata = {
-# 'engine1':mymodel.metadata1,
-# 'engine2':mymodel.metadata2
-# }
-target_metadata = {}
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def run_migrations_offline() -> None:
- """Run migrations in 'offline' mode.
-
- This configures the context with just a URL
- and not an Engine, though an Engine is acceptable
- here as well. By skipping the Engine creation
- we don't even need a DBAPI to be available.
-
- Calls to context.execute() here emit the given string to the
- script output.
-
- """
- # for the --sql use case, run migrations for each URL into
- # individual files.
-
- engines = {}
- for name in re.split(r",\s*", db_names):
- engines[name] = rec = {}
- rec["url"] = context.config.get_section_option(name, "sqlalchemy.url")
-
- for name, rec in engines.items():
- logger.info("Migrating database %s" % name)
- file_ = "%s.sql" % name
- logger.info("Writing output to %s" % file_)
- with open(file_, "w") as buffer:
- context.configure(
- url=rec["url"],
- output_buffer=buffer,
- target_metadata=target_metadata.get(name),
- literal_binds=True,
- dialect_opts={"paramstyle": "named"},
- )
- with context.begin_transaction():
- context.run_migrations(engine_name=name)
-
-
-def run_migrations_online() -> None:
- """Run migrations in 'online' mode.
-
- In this scenario we need to create an Engine
- and associate a connection with the context.
-
- """
-
- # for the direct-to-DB use case, start a transaction on all
- # engines, then run all migrations, then commit all transactions.
-
- engines = {}
- for name in re.split(r",\s*", db_names):
- engines[name] = rec = {}
- rec["engine"] = engine_from_config(
- context.config.get_section(name, {}),
- prefix="sqlalchemy.",
- poolclass=pool.NullPool,
- )
-
- for name, rec in engines.items():
- engine = rec["engine"]
- rec["connection"] = conn = engine.connect()
-
- if USE_TWOPHASE:
- rec["transaction"] = conn.begin_twophase()
- else:
- rec["transaction"] = conn.begin()
-
- try:
- for name, rec in engines.items():
- logger.info("Migrating database %s" % name)
- context.configure(
- connection=rec["connection"],
- upgrade_token="%s_upgrades" % name,
- downgrade_token="%s_downgrades" % name,
- target_metadata=target_metadata.get(name),
- )
- context.run_migrations(engine_name=name)
-
- if USE_TWOPHASE:
- for rec in engines.values():
- rec["transaction"].prepare()
-
- for rec in engines.values():
- rec["transaction"].commit()
- except:
- for rec in engines.values():
- rec["transaction"].rollback()
- raise
- finally:
- for rec in engines.values():
- rec["connection"].close()
-
-
-if context.is_offline_mode():
- run_migrations_offline()
-else:
- run_migrations_online()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako
deleted file mode 100644
index 8e667d8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako
+++ /dev/null
@@ -1,51 +0,0 @@
-<%!
-import re
-
-%>"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision: str = ${repr(up_revision)}
-down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
-branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
-depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
-
-
-def upgrade(engine_name: str) -> None:
- """Upgrade schema."""
- globals()["upgrade_%s" % engine_name]()
-
-
-def downgrade(engine_name: str) -> None:
- """Downgrade schema."""
- globals()["downgrade_%s" % engine_name]()
-
-<%
- db_names = config.get_main_option("databases")
-%>
-
-## generate an "upgrade_() / downgrade_()" function
-## for each database name in the ini file.
-
-% for db_name in re.split(r',\s*', db_names):
-
-def upgrade_${db_name}() -> None:
- """Upgrade ${db_name} schema."""
- ${context.get("%s_upgrades" % db_name, "pass")}
-
-
-def downgrade_${db_name}() -> None:
- """Downgrade ${db_name} schema."""
- ${context.get("%s_downgrades" % db_name, "pass")}
-
-% endfor
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/README b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/README
deleted file mode 100644
index fdacc05..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/README
+++ /dev/null
@@ -1 +0,0 @@
-pyproject configuration, based on the generic configuration.
\ No newline at end of file
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 1ed573c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako
deleted file mode 100644
index 3d10f0e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako
+++ /dev/null
@@ -1,44 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-
-# database URL. This is consumed by the user-maintained env.py script only.
-# other means of configuring database URLs may be customized within the env.py
-# file.
-sqlalchemy.url = driver://user:pass@localhost/dbname
-
-
-# Logging configuration
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARNING
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py
deleted file mode 100644
index 36112a3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from logging.config import fileConfig
-
-from sqlalchemy import engine_from_config
-from sqlalchemy import pool
-
-from alembic import context
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
- fileConfig(config.config_file_name)
-
-# add your model's MetaData object here
-# for 'autogenerate' support
-# from myapp import mymodel
-# target_metadata = mymodel.Base.metadata
-target_metadata = None
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def run_migrations_offline() -> None:
- """Run migrations in 'offline' mode.
-
- This configures the context with just a URL
- and not an Engine, though an Engine is acceptable
- here as well. By skipping the Engine creation
- we don't even need a DBAPI to be available.
-
- Calls to context.execute() here emit the given string to the
- script output.
-
- """
- url = config.get_main_option("sqlalchemy.url")
- context.configure(
- url=url,
- target_metadata=target_metadata,
- literal_binds=True,
- dialect_opts={"paramstyle": "named"},
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-def run_migrations_online() -> None:
- """Run migrations in 'online' mode.
-
- In this scenario we need to create an Engine
- and associate a connection with the context.
-
- """
- connectable = engine_from_config(
- config.get_section(config.config_ini_section, {}),
- prefix="sqlalchemy.",
- poolclass=pool.NullPool,
- )
-
- with connectable.connect() as connection:
- context.configure(
- connection=connection, target_metadata=target_metadata
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-if context.is_offline_mode():
- run_migrations_offline()
-else:
- run_migrations_online()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako
deleted file mode 100644
index 7edd43b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako
+++ /dev/null
@@ -1,84 +0,0 @@
-[tool.alembic]
-
-# path to migration scripts.
-# this is typically a path given in POSIX (e.g. forward slashes)
-# format, relative to the token %(here)s which refers to the location of this
-# ini file
-script_location = "${script_location}"
-
-# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
-# Uncomment the line below if you want the files to be prepended with date and time
-# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
-# for all available tokens
-# file_template = "%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s"
-# Or organize into date-based subdirectories (requires recursive_version_locations = true)
-# file_template = "%%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s"
-
-# additional paths to be prepended to sys.path. defaults to the current working directory.
-prepend_sys_path = [
- "."
-]
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the tzdata library which can be installed by adding
-# `alembic[tz]` to the pip requirements.
-# string value is passed to ZoneInfo()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to /versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# version_locations = [
-# "%(here)s/alembic/versions",
-# "%(here)s/foo/bar"
-# ]
-
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# new in Alembic version 1.10
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = "utf-8"
-
-# This section defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-# [[tool.alembic.post_write_hooks]]
-# format using "black" - use the console_scripts runner,
-# against the "black" entrypoint
-# name = "black"
-# type = "console_scripts"
-# entrypoint = "black"
-# options = "-l 79 REVISION_SCRIPT_FILENAME"
-#
-# [[tool.alembic.post_write_hooks]]
-# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
-# name = "ruff"
-# type = "module"
-# module = "ruff"
-# options = "check --fix REVISION_SCRIPT_FILENAME"
-#
-# [[tool.alembic.post_write_hooks]]
-# Alternatively, use the exec runner to execute a binary found on your PATH
-# name = "ruff"
-# type = "exec"
-# executable = "ruff"
-# options = "check --fix REVISION_SCRIPT_FILENAME"
-
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako
deleted file mode 100644
index 1101630..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako
+++ /dev/null
@@ -1,28 +0,0 @@
-"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision: str = ${repr(up_revision)}
-down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
-branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
-depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
-
-
-def upgrade() -> None:
- """Upgrade schema."""
- ${upgrades if upgrades else "pass"}
-
-
-def downgrade() -> None:
- """Downgrade schema."""
- ${downgrades if downgrades else "pass"}
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README
deleted file mode 100644
index dfd718d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README
+++ /dev/null
@@ -1 +0,0 @@
-pyproject configuration, with an async dbapi.
\ No newline at end of file
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 6d82574..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/alembic.ini.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/alembic.ini.mako
deleted file mode 100644
index 3d10f0e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/alembic.ini.mako
+++ /dev/null
@@ -1,44 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-
-# database URL. This is consumed by the user-maintained env.py script only.
-# other means of configuring database URLs may be customized within the env.py
-# file.
-sqlalchemy.url = driver://user:pass@localhost/dbname
-
-
-# Logging configuration
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARNING
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/env.py
deleted file mode 100644
index 9f2d519..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/env.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import asyncio
-from logging.config import fileConfig
-
-from sqlalchemy import pool
-from sqlalchemy.engine import Connection
-from sqlalchemy.ext.asyncio import async_engine_from_config
-
-from alembic import context
-
-# this is the Alembic Config object, which provides
-# access to the values within the .ini file in use.
-config = context.config
-
-# Interpret the config file for Python logging.
-# This line sets up loggers basically.
-if config.config_file_name is not None:
- fileConfig(config.config_file_name)
-
-# add your model's MetaData object here
-# for 'autogenerate' support
-# from myapp import mymodel
-# target_metadata = mymodel.Base.metadata
-target_metadata = None
-
-# other values from the config, defined by the needs of env.py,
-# can be acquired:
-# my_important_option = config.get_main_option("my_important_option")
-# ... etc.
-
-
-def run_migrations_offline() -> None:
- """Run migrations in 'offline' mode.
-
- This configures the context with just a URL
- and not an Engine, though an Engine is acceptable
- here as well. By skipping the Engine creation
- we don't even need a DBAPI to be available.
-
- Calls to context.execute() here emit the given string to the
- script output.
-
- """
- url = config.get_main_option("sqlalchemy.url")
- context.configure(
- url=url,
- target_metadata=target_metadata,
- literal_binds=True,
- dialect_opts={"paramstyle": "named"},
- )
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-def do_run_migrations(connection: Connection) -> None:
- context.configure(connection=connection, target_metadata=target_metadata)
-
- with context.begin_transaction():
- context.run_migrations()
-
-
-async def run_async_migrations() -> None:
- """In this scenario we need to create an Engine
- and associate a connection with the context.
-
- """
-
- connectable = async_engine_from_config(
- config.get_section(config.config_ini_section, {}),
- prefix="sqlalchemy.",
- poolclass=pool.NullPool,
- )
-
- async with connectable.connect() as connection:
- await connection.run_sync(do_run_migrations)
-
- await connectable.dispose()
-
-
-def run_migrations_online() -> None:
- """Run migrations in 'online' mode."""
-
- asyncio.run(run_async_migrations())
-
-
-if context.is_offline_mode():
- run_migrations_offline()
-else:
- run_migrations_online()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako
deleted file mode 100644
index 7edd43b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako
+++ /dev/null
@@ -1,84 +0,0 @@
-[tool.alembic]
-
-# path to migration scripts.
-# this is typically a path given in POSIX (e.g. forward slashes)
-# format, relative to the token %(here)s which refers to the location of this
-# ini file
-script_location = "${script_location}"
-
-# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
-# Uncomment the line below if you want the files to be prepended with date and time
-# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
-# for all available tokens
-# file_template = "%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s"
-# Or organize into date-based subdirectories (requires recursive_version_locations = true)
-# file_template = "%%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s"
-
-# additional paths to be prepended to sys.path. defaults to the current working directory.
-prepend_sys_path = [
- "."
-]
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-# If specified, requires the tzdata library which can be installed by adding
-# `alembic[tz]` to the pip requirements.
-# string value is passed to ZoneInfo()
-# leave blank for localtime
-# timezone =
-
-# max length of characters to apply to the "slug" field
-# truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to /versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# version_locations = [
-# "%(here)s/alembic/versions",
-# "%(here)s/foo/bar"
-# ]
-
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# new in Alembic version 1.10
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-# output_encoding = "utf-8"
-
-# This section defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-# [[tool.alembic.post_write_hooks]]
-# format using "black" - use the console_scripts runner,
-# against the "black" entrypoint
-# name = "black"
-# type = "console_scripts"
-# entrypoint = "black"
-# options = "-l 79 REVISION_SCRIPT_FILENAME"
-#
-# [[tool.alembic.post_write_hooks]]
-# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
-# name = "ruff"
-# type = "module"
-# module = "ruff"
-# options = "check --fix REVISION_SCRIPT_FILENAME"
-#
-# [[tool.alembic.post_write_hooks]]
-# Alternatively, use the exec runner to execute a binary found on your PATH
-# name = "ruff"
-# type = "exec"
-# executable = "ruff"
-# options = "check --fix REVISION_SCRIPT_FILENAME"
-
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako b/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako
deleted file mode 100644
index 1101630..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako
+++ /dev/null
@@ -1,28 +0,0 @@
-"""${message}
-
-Revision ID: ${up_revision}
-Revises: ${down_revision | comma,n}
-Create Date: ${create_date}
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-${imports if imports else ""}
-
-# revision identifiers, used by Alembic.
-revision: str = ${repr(up_revision)}
-down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
-branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
-depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
-
-
-def upgrade() -> None:
- """Upgrade schema."""
- ${upgrades if upgrades else "pass"}
-
-
-def downgrade() -> None:
- """Downgrade schema."""
- ${downgrades if downgrades else "pass"}
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__init__.py
deleted file mode 100644
index 3291508..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from sqlalchemy.testing import config
-from sqlalchemy.testing import emits_warning
-from sqlalchemy.testing import engines
-from sqlalchemy.testing import exclusions
-from sqlalchemy.testing import mock
-from sqlalchemy.testing import provide_metadata
-from sqlalchemy.testing import skip_if
-from sqlalchemy.testing import uses_deprecated
-from sqlalchemy.testing.config import combinations
-from sqlalchemy.testing.config import fixture
-from sqlalchemy.testing.config import requirements as requires
-from sqlalchemy.testing.config import Variation
-from sqlalchemy.testing.config import variation
-
-from .assertions import assert_raises
-from .assertions import assert_raises_message
-from .assertions import emits_python_deprecation_warning
-from .assertions import eq_
-from .assertions import eq_ignore_whitespace
-from .assertions import expect_deprecated
-from .assertions import expect_raises
-from .assertions import expect_raises_message
-from .assertions import expect_sqlalchemy_deprecated
-from .assertions import expect_sqlalchemy_deprecated_20
-from .assertions import expect_warnings
-from .assertions import is_
-from .assertions import is_false
-from .assertions import is_not_
-from .assertions import is_true
-from .assertions import ne_
-from .fixtures import TestBase
-from .util import resolve_lambda
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index e3615c6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc
deleted file mode 100644
index 97f483f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc
deleted file mode 100644
index 7be5469..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc
deleted file mode 100644
index 670abd4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc
deleted file mode 100644
index 1a77f20..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/schemacompare.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/schemacompare.cpython-312.pyc
deleted file mode 100644
index d1f9f09..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/schemacompare.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc
deleted file mode 100644
index 48f02ec..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc
deleted file mode 100644
index 670d29d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/assertions.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/assertions.py
deleted file mode 100644
index e76103d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/assertions.py
+++ /dev/null
@@ -1,180 +0,0 @@
-from __future__ import annotations
-
-import contextlib
-import re
-import sys
-from typing import Any
-from typing import Dict
-
-from sqlalchemy import exc as sa_exc
-from sqlalchemy.engine import default
-from sqlalchemy.engine import URL
-from sqlalchemy.testing.assertions import _expect_warnings
-from sqlalchemy.testing.assertions import eq_ # noqa
-from sqlalchemy.testing.assertions import is_ # noqa
-from sqlalchemy.testing.assertions import is_false # noqa
-from sqlalchemy.testing.assertions import is_not_ # noqa
-from sqlalchemy.testing.assertions import is_true # noqa
-from sqlalchemy.testing.assertions import ne_ # noqa
-from sqlalchemy.util import decorator
-
-
-def _assert_proper_exception_context(exception):
- """assert that any exception we're catching does not have a __context__
- without a __cause__, and that __suppress_context__ is never set.
-
- Python 3 will report nested as exceptions as "during the handling of
- error X, error Y occurred". That's not what we want to do. we want
- these exceptions in a cause chain.
-
- """
-
- if (
- exception.__context__ is not exception.__cause__
- and not exception.__suppress_context__
- ):
- assert False, (
- "Exception %r was correctly raised but did not set a cause, "
- "within context %r as its cause."
- % (exception, exception.__context__)
- )
-
-
-def assert_raises(except_cls, callable_, *args, **kw):
- return _assert_raises(except_cls, callable_, args, kw, check_context=True)
-
-
-def assert_raises_context_ok(except_cls, callable_, *args, **kw):
- return _assert_raises(except_cls, callable_, args, kw)
-
-
-def assert_raises_message(except_cls, msg, callable_, *args, **kwargs):
- return _assert_raises(
- except_cls, callable_, args, kwargs, msg=msg, check_context=True
- )
-
-
-def assert_raises_message_context_ok(
- except_cls, msg, callable_, *args, **kwargs
-):
- return _assert_raises(except_cls, callable_, args, kwargs, msg=msg)
-
-
-def _assert_raises(
- except_cls, callable_, args, kwargs, msg=None, check_context=False
-):
- with _expect_raises(except_cls, msg, check_context) as ec:
- callable_(*args, **kwargs)
- return ec.error
-
-
-class _ErrorContainer:
- error: Any = None
-
-
-@contextlib.contextmanager
-def _expect_raises(
- except_cls, msg=None, check_context=False, text_exact=False
-):
- ec = _ErrorContainer()
- if check_context:
- are_we_already_in_a_traceback = sys.exc_info()[0]
- try:
- yield ec
- success = False
- except except_cls as err:
- ec.error = err
- success = True
- if msg is not None:
- if text_exact:
- assert str(err) == msg, f"{msg} != {err}"
- else:
- assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}"
- if check_context and not are_we_already_in_a_traceback:
- _assert_proper_exception_context(err)
- print(str(err).encode("utf-8"))
-
- # assert outside the block so it works for AssertionError too !
- assert success, "Callable did not raise an exception"
-
-
-def expect_raises(except_cls, check_context=True):
- return _expect_raises(except_cls, check_context=check_context)
-
-
-def expect_raises_message(
- except_cls, msg, check_context=True, text_exact=False
-):
- return _expect_raises(
- except_cls, msg=msg, check_context=check_context, text_exact=text_exact
- )
-
-
-def eq_ignore_whitespace(a, b, msg=None):
- a = re.sub(r"^\s+?|\n", "", a)
- a = re.sub(r" {2,}", " ", a)
- b = re.sub(r"^\s+?|\n", "", b)
- b = re.sub(r" {2,}", " ", b)
-
- assert a == b, msg or "%r != %r" % (a, b)
-
-
-_dialect_mods: Dict[Any, Any] = {}
-
-
-def _get_dialect(name):
- if name is None or name == "default":
- return default.DefaultDialect()
- else:
- d = URL.create(name).get_dialect()()
-
- if name == "postgresql":
- d.implicit_returning = True
- elif name == "mssql":
- d.legacy_schema_aliasing = False
- d.default_schema_name = "dbo"
- return d
-
-
-def expect_warnings(*messages, **kw):
- """Context manager which expects one or more warnings.
-
- With no arguments, squelches all SAWarnings emitted via
- sqlalchemy.util.warn and sqlalchemy.util.warn_limited. Otherwise
- pass string expressions that will match selected warnings via regex;
- all non-matching warnings are sent through.
-
- The expect version **asserts** that the warnings were in fact seen.
-
- Note that the test suite sets SAWarning warnings to raise exceptions.
-
- """
- return _expect_warnings(Warning, messages, **kw)
-
-
-def emits_python_deprecation_warning(*messages):
- """Decorator form of expect_warnings().
-
- Note that emits_warning does **not** assert that the warnings
- were in fact seen.
-
- """
-
- @decorator
- def decorate(fn, *args, **kw):
- with _expect_warnings(DeprecationWarning, assert_=False, *messages):
- return fn(*args, **kw)
-
- return decorate
-
-
-def expect_deprecated(*messages, **kw):
- return _expect_warnings(DeprecationWarning, messages, **kw)
-
-
-def expect_sqlalchemy_deprecated(*messages, **kw):
- return _expect_warnings(sa_exc.SADeprecationWarning, messages, **kw)
-
-
-def expect_sqlalchemy_deprecated_20(*messages, **kw):
- return _expect_warnings(sa_exc.RemovedIn20Warning, messages, **kw)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/env.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/env.py
deleted file mode 100644
index ad4de78..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/env.py
+++ /dev/null
@@ -1,581 +0,0 @@
-import importlib.machinery
-import logging
-import os
-from pathlib import Path
-import shutil
-import textwrap
-
-from sqlalchemy.testing import config
-from sqlalchemy.testing import provision
-
-from . import util as testing_util
-from .. import command
-from .. import script
-from .. import util
-from ..script import Script
-from ..script import ScriptDirectory
-
-
-def _get_staging_directory():
- if provision.FOLLOWER_IDENT:
- return f"scratch_{provision.FOLLOWER_IDENT}"
- else:
- return "scratch"
-
-
-_restore_log = None
-
-
-def _replace_logger():
- global _restore_log
- if _restore_log is None:
- _restore_log = (logging.root, logging.Logger.manager)
- logging.root = logging.RootLogger(logging.WARNING)
- logging.Logger.root = logging.root
- logging.Logger.manager = logging.Manager(logging.root)
-
-
-def _restore_logger():
- global _restore_log
-
- if _restore_log is not None:
- logging.root, logging.Logger.manager = _restore_log
- logging.Logger.root = logging.root
- _restore_log = None
-
-
-def staging_env(create=True, template="generic", sourceless=False):
- _replace_logger()
- cfg = _testing_config()
- if create:
- path = _join_path(_get_staging_directory(), "scripts")
- assert not os.path.exists(path), (
- "staging directory %s already exists; poor cleanup?" % path
- )
-
- command.init(cfg, path, template=template)
- if sourceless:
- try:
- # do an import so that a .pyc/.pyo is generated.
- util.load_python_file(path, "env.py")
- except AttributeError:
- # we don't have the migration context set up yet
- # so running the .env py throws this exception.
- # theoretically we could be using py_compiler here to
- # generate .pyc/.pyo without importing but not really
- # worth it.
- pass
- assert sourceless in (
- "pep3147_envonly",
- "simple",
- "pep3147_everything",
- ), sourceless
- make_sourceless(
- _join_path(path, "env.py"),
- "pep3147" if "pep3147" in sourceless else "simple",
- )
-
- sc = script.ScriptDirectory.from_config(cfg)
- return sc
-
-
-def clear_staging_env():
- from sqlalchemy.testing import engines
-
- engines.testing_reaper.close_all()
- shutil.rmtree(_get_staging_directory(), True)
- _restore_logger()
-
-
-def script_file_fixture(txt):
- dir_ = _join_path(_get_staging_directory(), "scripts")
- path = _join_path(dir_, "script.py.mako")
- with open(path, "w") as f:
- f.write(txt)
-
-
-def env_file_fixture(txt):
- dir_ = _join_path(_get_staging_directory(), "scripts")
- txt = (
- """
-from alembic import context
-
-config = context.config
-"""
- + txt
- )
-
- path = _join_path(dir_, "env.py")
- pyc_path = util.pyc_file_from_path(path)
- if pyc_path:
- os.unlink(pyc_path)
-
- with open(path, "w") as f:
- f.write(txt)
-
-
-def _sqlite_file_db(tempname="foo.db", future=False, scope=None, **options):
- dir_ = _join_path(_get_staging_directory(), "scripts")
- url = "sqlite:///%s/%s" % (dir_, tempname)
- if scope:
- options["scope"] = scope
- return testing_util.testing_engine(url=url, future=future, options=options)
-
-
-def _sqlite_testing_config(sourceless=False, future=False):
- dir_ = _join_path(_get_staging_directory(), "scripts")
- url = f"sqlite:///{dir_}/foo.db"
-
- sqlalchemy_future = future or ("future" in config.db.__class__.__module__)
-
- return _write_config_file(
- f"""
-[alembic]
-script_location = {dir_}
-sqlalchemy.url = {url}
-sourceless = {"true" if sourceless else "false"}
-{"sqlalchemy.future = true" if sqlalchemy_future else ""}
-
-[loggers]
-keys = root,sqlalchemy
-
-[handlers]
-keys = console
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = DEBUG
-handlers =
-qualname = sqlalchemy.engine
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatters]
-keys = generic
-
-[formatter_generic]
-format = %%(levelname)-5.5s [%%(name)s] %%(message)s
-datefmt = %%H:%%M:%%S
- """
- )
-
-
-def _multi_dir_testing_config(sourceless=False, extra_version_location=""):
- dir_ = _join_path(_get_staging_directory(), "scripts")
- sqlalchemy_future = "future" in config.db.__class__.__module__
-
- url = "sqlite:///%s/foo.db" % dir_
-
- return _write_config_file(
- f"""
-[alembic]
-script_location = {dir_}
-sqlalchemy.url = {url}
-sqlalchemy.future = {"true" if sqlalchemy_future else "false"}
-sourceless = {"true" if sourceless else "false"}
-path_separator = space
-version_locations = %(here)s/model1/ %(here)s/model2/ %(here)s/model3/ \
-{extra_version_location}
-
-[loggers]
-keys = root
-
-[handlers]
-keys = console
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatters]
-keys = generic
-
-[formatter_generic]
-format = %%(levelname)-5.5s [%%(name)s] %%(message)s
-datefmt = %%H:%%M:%%S
- """
- )
-
-
-def _no_sql_pyproject_config(dialect="postgresql", directives=""):
- """use a postgresql url with no host so that
- connections guaranteed to fail"""
- dir_ = _join_path(_get_staging_directory(), "scripts")
-
- return _write_toml_config(
- f"""
-[tool.alembic]
-script_location ="{dir_}"
-{textwrap.dedent(directives)}
-
- """,
- f"""
-[alembic]
-sqlalchemy.url = {dialect}://
-
-[loggers]
-keys = root
-
-[handlers]
-keys = console
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatters]
-keys = generic
-
-[formatter_generic]
-format = %%(levelname)-5.5s [%%(name)s] %%(message)s
-datefmt = %%H:%%M:%%S
-
-""",
- )
-
-
-def _no_sql_testing_config(dialect="postgresql", directives=""):
- """use a postgresql url with no host so that
- connections guaranteed to fail"""
- dir_ = _join_path(_get_staging_directory(), "scripts")
- return _write_config_file(
- f"""
-[alembic]
-script_location ={dir_}
-sqlalchemy.url = {dialect}://
-{directives}
-
-[loggers]
-keys = root
-
-[handlers]
-keys = console
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatters]
-keys = generic
-
-[formatter_generic]
-format = %%(levelname)-5.5s [%%(name)s] %%(message)s
-datefmt = %%H:%%M:%%S
-
-"""
- )
-
-
-def _write_toml_config(tomltext, initext):
- cfg = _write_config_file(initext)
- with open(cfg.toml_file_name, "w") as f:
- f.write(tomltext)
- return cfg
-
-
-def _write_config_file(text):
- cfg = _testing_config()
- with open(cfg.config_file_name, "w") as f:
- f.write(text)
- return cfg
-
-
-def _testing_config():
- from alembic.config import Config
-
- if not os.access(_get_staging_directory(), os.F_OK):
- os.mkdir(_get_staging_directory())
- return Config(
- _join_path(_get_staging_directory(), "test_alembic.ini"),
- _join_path(_get_staging_directory(), "pyproject.toml"),
- )
-
-
-def write_script(
- scriptdir, rev_id, content, encoding="ascii", sourceless=False
-):
- old = scriptdir.revision_map.get_revision(rev_id)
- path = old.path
-
- content = textwrap.dedent(content)
- if encoding:
- content = content.encode(encoding)
- with open(path, "wb") as fp:
- fp.write(content)
- pyc_path = util.pyc_file_from_path(path)
- if pyc_path:
- os.unlink(pyc_path)
- script = Script._from_path(scriptdir, path)
- old = scriptdir.revision_map.get_revision(script.revision)
- if old.down_revision != script.down_revision:
- raise Exception("Can't change down_revision on a refresh operation.")
- scriptdir.revision_map.add_revision(script, _replace=True)
-
- if sourceless:
- make_sourceless(
- path, "pep3147" if sourceless == "pep3147_everything" else "simple"
- )
-
-
-def make_sourceless(path, style):
- import py_compile
-
- py_compile.compile(path)
-
- if style == "simple":
- pyc_path = util.pyc_file_from_path(path)
- suffix = importlib.machinery.BYTECODE_SUFFIXES[0]
- filepath, ext = os.path.splitext(path)
- simple_pyc_path = filepath + suffix
- shutil.move(pyc_path, simple_pyc_path)
- pyc_path = simple_pyc_path
- else:
- assert style in ("pep3147", "simple")
- pyc_path = util.pyc_file_from_path(path)
-
- assert os.access(pyc_path, os.F_OK)
-
- os.unlink(path)
-
-
-def three_rev_fixture(cfg):
- a = util.rev_id()
- b = util.rev_id()
- c = util.rev_id()
-
- script = ScriptDirectory.from_config(cfg)
- script.generate_revision(a, "revision a", refresh=True, head="base")
- write_script(
- script,
- a,
- f"""\
-"Rev A"
-revision = '{a}'
-down_revision = None
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 1")
-
-
-def downgrade():
- op.execute("DROP STEP 1")
-
-""",
- )
-
- script.generate_revision(b, "revision b", refresh=True, head=a)
- write_script(
- script,
- b,
- f"""# coding: utf-8
-"Rev B, méil, %3"
-revision = '{b}'
-down_revision = '{a}'
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 2")
-
-
-def downgrade():
- op.execute("DROP STEP 2")
-
-""",
- encoding="utf-8",
- )
-
- script.generate_revision(c, "revision c", refresh=True, head=b)
- write_script(
- script,
- c,
- f"""\
-"Rev C"
-revision = '{c}'
-down_revision = '{b}'
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 3")
-
-
-def downgrade():
- op.execute("DROP STEP 3")
-
-""",
- )
- return a, b, c
-
-
-def multi_heads_fixture(cfg, a, b, c):
- """Create a multiple head fixture from the three-revs fixture"""
-
- # a->b->c
- # -> d -> e
- # -> f
- d = util.rev_id()
- e = util.rev_id()
- f = util.rev_id()
-
- script = ScriptDirectory.from_config(cfg)
- script.generate_revision(
- d, "revision d from b", head=b, splice=True, refresh=True
- )
- write_script(
- script,
- d,
- f"""\
-"Rev D"
-revision = '{d}'
-down_revision = '{b}'
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 4")
-
-
-def downgrade():
- op.execute("DROP STEP 4")
-
-""",
- )
-
- script.generate_revision(
- e, "revision e from d", head=d, splice=True, refresh=True
- )
- write_script(
- script,
- e,
- f"""\
-"Rev E"
-revision = '{e}'
-down_revision = '{d}'
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 5")
-
-
-def downgrade():
- op.execute("DROP STEP 5")
-
-""",
- )
-
- script.generate_revision(
- f, "revision f from b", head=b, splice=True, refresh=True
- )
- write_script(
- script,
- f,
- f"""\
-"Rev F"
-revision = '{f}'
-down_revision = '{b}'
-
-from alembic import op
-
-
-def upgrade():
- op.execute("CREATE STEP 6")
-
-
-def downgrade():
- op.execute("DROP STEP 6")
-
-""",
- )
-
- return d, e, f
-
-
-def _multidb_testing_config(engines):
- """alembic.ini fixture to work exactly with the 'multidb' template"""
-
- dir_ = _join_path(_get_staging_directory(), "scripts")
-
- sqlalchemy_future = "future" in config.db.__class__.__module__
-
- databases = ", ".join(engines.keys())
- engines = "\n\n".join(
- f"[{key}]\nsqlalchemy.url = {value.url}"
- for key, value in engines.items()
- )
-
- return _write_config_file(
- f"""
-[alembic]
-script_location = {dir_}
-sourceless = false
-sqlalchemy.future = {"true" if sqlalchemy_future else "false"}
-databases = {databases}
-
-{engines}
-[loggers]
-keys = root
-
-[handlers]
-keys = console
-
-[logger_root]
-level = WARNING
-handlers = console
-qualname =
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatters]
-keys = generic
-
-[formatter_generic]
-format = %%(levelname)-5.5s [%%(name)s] %%(message)s
-datefmt = %%H:%%M:%%S
- """
- )
-
-
-def _join_path(base: str, *more: str):
- return str(Path(base).joinpath(*more).as_posix())
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/fixtures.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/fixtures.py
deleted file mode 100644
index 73e4212..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/fixtures.py
+++ /dev/null
@@ -1,404 +0,0 @@
-from __future__ import annotations
-
-import configparser
-from contextlib import contextmanager
-import io
-import os
-import re
-import shutil
-from typing import Any
-from typing import Dict
-from typing import Generator
-from typing import Literal
-from typing import overload
-
-from sqlalchemy import Column
-from sqlalchemy import create_mock_engine
-from sqlalchemy import inspect
-from sqlalchemy import MetaData
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy import testing
-from sqlalchemy import text
-from sqlalchemy.testing import config
-from sqlalchemy.testing import mock
-from sqlalchemy.testing.assertions import eq_
-from sqlalchemy.testing.fixtures import FutureEngineMixin
-from sqlalchemy.testing.fixtures import TablesTest as SQLAlchemyTablesTest
-from sqlalchemy.testing.fixtures import TestBase as SQLAlchemyTestBase
-from sqlalchemy.testing.util import drop_all_tables_from_metadata
-
-import alembic
-from .assertions import _get_dialect
-from .env import _get_staging_directory
-from ..environment import EnvironmentContext
-from ..migration import MigrationContext
-from ..operations import Operations
-from ..util import sqla_compat
-from ..util.sqla_compat import sqla_2
-
-testing_config = configparser.ConfigParser()
-testing_config.read(["test.cfg"])
-
-
-class TestBase(SQLAlchemyTestBase):
- is_sqlalchemy_future = sqla_2
-
- @testing.fixture()
- def clear_staging_dir(self):
- yield
- location = _get_staging_directory()
- for filename in os.listdir(location):
- file_path = os.path.join(location, filename)
- if os.path.isfile(file_path) or os.path.islink(file_path):
- os.unlink(file_path)
- elif os.path.isdir(file_path):
- shutil.rmtree(file_path)
-
- @contextmanager
- def pushd(self, dirname) -> Generator[None, None, None]:
- current_dir = os.getcwd()
- try:
- os.chdir(dirname)
- yield
- finally:
- os.chdir(current_dir)
-
- @testing.fixture()
- def pop_alembic_config_env(self):
- yield
- os.environ.pop("ALEMBIC_CONFIG", None)
-
- @testing.fixture()
- def ops_context(self, migration_context):
- with migration_context.begin_transaction(_per_migration=True):
- yield Operations(migration_context)
-
- @testing.fixture
- def migration_context(self, connection):
- return MigrationContext.configure(
- connection, opts=dict(transaction_per_migration=True)
- )
-
- @testing.fixture
- def as_sql_migration_context(self, connection):
- return MigrationContext.configure(
- connection, opts=dict(transaction_per_migration=True, as_sql=True)
- )
-
- @testing.fixture
- def connection(self):
- global _connection_fixture_connection
-
- with config.db.connect() as conn:
- _connection_fixture_connection = conn
- yield conn
-
- _connection_fixture_connection = None
-
- @testing.fixture
- def restore_operations(self):
- """Restore runners for modified operations"""
-
- saved_impls = None
- op_cls = None
-
- def _save_attrs(_op_cls):
- nonlocal saved_impls, op_cls
- saved_impls = _op_cls._to_impl._registry.copy()
- op_cls = _op_cls
-
- yield _save_attrs
-
- if op_cls is not None and saved_impls is not None:
- op_cls._to_impl._registry = saved_impls
-
- @config.fixture()
- def metadata(self, request):
- """Provide bound MetaData for a single test, dropping afterwards."""
-
- from sqlalchemy.sql import schema
-
- metadata = schema.MetaData()
- request.instance.metadata = metadata
- yield metadata
- del request.instance.metadata
-
- if (
- _connection_fixture_connection
- and _connection_fixture_connection.in_transaction()
- ):
- trans = _connection_fixture_connection.get_transaction()
- trans.rollback()
- with _connection_fixture_connection.begin():
- drop_all_tables_from_metadata(
- metadata, _connection_fixture_connection
- )
- else:
- drop_all_tables_from_metadata(metadata, config.db)
-
-
-_connection_fixture_connection = None
-
-
-class TablesTest(TestBase, SQLAlchemyTablesTest):
- pass
-
-
-FutureEngineMixin.is_sqlalchemy_future = True
-
-
-def capture_db(dialect="postgresql://"):
- buf = []
-
- def dump(sql, *multiparams, **params):
- buf.append(str(sql.compile(dialect=engine.dialect)))
-
- engine = create_mock_engine(dialect, dump)
- return engine, buf
-
-
-_engs: Dict[Any, Any] = {}
-
-
-@overload
-@contextmanager
-def capture_context_buffer(
- bytes_io: Literal[True], **kw: Any
-) -> Generator[io.BytesIO, None, None]: ...
-
-
-@overload
-@contextmanager
-def capture_context_buffer(
- **kw: Any,
-) -> Generator[io.StringIO, None, None]: ...
-
-
-@contextmanager
-def capture_context_buffer(
- **kw: Any,
-) -> Generator[io.StringIO | io.BytesIO, None, None]:
- if kw.pop("bytes_io", False):
- buf = io.BytesIO()
- else:
- buf = io.StringIO()
-
- kw.update({"dialect_name": "sqlite", "output_buffer": buf})
- conf = EnvironmentContext.configure
-
- def configure(*arg, **opt):
- opt.update(**kw)
- return conf(*arg, **opt)
-
- with mock.patch.object(EnvironmentContext, "configure", configure):
- yield buf
-
-
-@contextmanager
-def capture_engine_context_buffer(
- **kw: Any,
-) -> Generator[io.StringIO, None, None]:
- from .env import _sqlite_file_db
- from sqlalchemy import event
-
- buf = io.StringIO()
-
- eng = _sqlite_file_db()
-
- conn = eng.connect()
-
- @event.listens_for(conn, "before_cursor_execute")
- def bce(conn, cursor, statement, parameters, context, executemany):
- buf.write(statement + "\n")
-
- kw.update({"connection": conn})
- conf = EnvironmentContext.configure
-
- def configure(*arg, **opt):
- opt.update(**kw)
- return conf(*arg, **opt)
-
- with mock.patch.object(EnvironmentContext, "configure", configure):
- yield buf
-
-
-def op_fixture(
- dialect="default",
- as_sql=False,
- naming_convention=None,
- literal_binds=False,
- native_boolean=None,
-):
- opts = {}
- if naming_convention:
- opts["target_metadata"] = MetaData(naming_convention=naming_convention)
-
- class buffer_:
- def __init__(self):
- self.lines = []
-
- def write(self, msg):
- msg = msg.strip()
- msg = re.sub(r"[\n\t]", "", msg)
- if as_sql:
- # the impl produces soft tabs,
- # so search for blocks of 4 spaces
- msg = re.sub(r" ", "", msg)
- msg = re.sub(r"\;\n*$", "", msg)
-
- self.lines.append(msg)
-
- def flush(self):
- pass
-
- buf = buffer_()
-
- class ctx(MigrationContext):
- def get_buf(self):
- return buf
-
- def clear_assertions(self):
- buf.lines[:] = []
-
- def assert_(self, *sql):
- # TODO: make this more flexible about
- # whitespace and such
- eq_(buf.lines, [re.sub(r"[\n\t]", "", s) for s in sql])
-
- def assert_contains(self, sql):
- for stmt in buf.lines:
- if re.sub(r"[\n\t]", "", sql) in stmt:
- return
- else:
- assert False, "Could not locate fragment %r in %r" % (
- sql,
- buf.lines,
- )
-
- if as_sql:
- opts["as_sql"] = as_sql
- if literal_binds:
- opts["literal_binds"] = literal_binds
-
- ctx_dialect = _get_dialect(dialect)
- if native_boolean is not None:
- ctx_dialect.supports_native_boolean = native_boolean
- # this is new as of SQLAlchemy 1.2.7 and is used by SQL Server,
- # which breaks assumptions in the alembic test suite
- ctx_dialect.non_native_boolean_check_constraint = True
- if not as_sql:
-
- def execute(stmt, *multiparam, **param):
- if isinstance(stmt, str):
- stmt = text(stmt)
- assert stmt.supports_execution
- sql = str(stmt.compile(dialect=ctx_dialect))
-
- buf.write(sql)
-
- connection = mock.Mock(dialect=ctx_dialect, execute=execute)
- else:
- opts["output_buffer"] = buf
- connection = None
- context = ctx(ctx_dialect, connection, opts)
-
- alembic.op._proxy = Operations(context)
- return context
-
-
-class AlterColRoundTripFixture:
- # since these tests are about syntax, use more recent SQLAlchemy as some of
- # the type / server default compare logic might not work on older
- # SQLAlchemy versions as seems to be the case for SQLAlchemy 1.1 on Oracle
-
- __requires__ = ("alter_column",)
-
- def setUp(self):
- self.conn = config.db.connect()
- self.ctx = MigrationContext.configure(self.conn)
- self.op = Operations(self.ctx)
- self.metadata = MetaData()
-
- def _compare_type(self, t1, t2):
- c1 = Column("q", t1)
- c2 = Column("q", t2)
- assert not self.ctx.impl.compare_type(
- c1, c2
- ), "Type objects %r and %r didn't compare as equivalent" % (t1, t2)
-
- def _compare_server_default(self, t1, s1, t2, s2):
- c1 = Column("q", t1, server_default=s1)
- c2 = Column("q", t2, server_default=s2)
- assert not self.ctx.impl.compare_server_default(
- c1, c2, s2, s1
- ), "server defaults %r and %r didn't compare as equivalent" % (s1, s2)
-
- def tearDown(self):
- sqla_compat._safe_rollback_connection_transaction(self.conn)
- with self.conn.begin():
- self.metadata.drop_all(self.conn)
- self.conn.close()
-
- def _run_alter_col(self, from_, to_, compare=None):
- column = Column(
- from_.get("name", "colname"),
- from_.get("type", String(10)),
- nullable=from_.get("nullable", True),
- server_default=from_.get("server_default", None),
- # comment=from_.get("comment", None)
- )
- t = Table("x", self.metadata, column)
-
- with sqla_compat._ensure_scope_for_ddl(self.conn):
- t.create(self.conn)
- insp = inspect(self.conn)
- old_col = insp.get_columns("x")[0]
-
- # TODO: conditional comment support
- self.op.alter_column(
- "x",
- column.name,
- existing_type=column.type,
- existing_server_default=(
- column.server_default
- if column.server_default is not None
- else False
- ),
- existing_nullable=True if column.nullable else False,
- # existing_comment=column.comment,
- nullable=to_.get("nullable", None),
- # modify_comment=False,
- server_default=to_.get("server_default", False),
- new_column_name=to_.get("name", None),
- type_=to_.get("type", None),
- )
-
- insp = inspect(self.conn)
- new_col = insp.get_columns("x")[0]
-
- if compare is None:
- compare = to_
-
- eq_(
- new_col["name"],
- compare["name"] if "name" in compare else column.name,
- )
- self._compare_type(
- new_col["type"], compare.get("type", old_col["type"])
- )
- eq_(new_col["nullable"], compare.get("nullable", column.nullable))
- self._compare_server_default(
- new_col["type"],
- new_col.get("default", None),
- compare.get("type", old_col["type"]),
- (
- compare["server_default"].text
- if "server_default" in compare
- else (
- column.server_default.arg.text
- if column.server_default is not None
- else None
- )
- ),
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index aa590f4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc
deleted file mode 100644
index aada427..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/bootstrap.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/bootstrap.py
deleted file mode 100644
index d4a2c55..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/plugin/bootstrap.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""
-Bootstrapper for test framework plugins.
-
-"""
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/requirements.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/requirements.py
deleted file mode 100644
index 1b217c9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/requirements.py
+++ /dev/null
@@ -1,189 +0,0 @@
-from sqlalchemy.testing.requirements import Requirements
-
-from alembic import util
-from ..testing import exclusions
-
-
-class SuiteRequirements(Requirements):
- @property
- def schemas(self):
- """Target database must support external schemas, and have one
- named 'test_schema'."""
-
- return exclusions.open()
-
- @property
- def autocommit_isolation(self):
- """target database should support 'AUTOCOMMIT' isolation level"""
-
- return exclusions.closed()
-
- @property
- def materialized_views(self):
- """needed for sqlalchemy compat"""
- return exclusions.closed()
-
- @property
- def unique_constraint_reflection(self):
- def doesnt_have_check_uq_constraints(config):
- from sqlalchemy import inspect
-
- insp = inspect(config.db)
- try:
- insp.get_unique_constraints("x")
- except NotImplementedError:
- return True
- except TypeError:
- return True
- except Exception:
- pass
- return False
-
- return exclusions.skip_if(doesnt_have_check_uq_constraints)
-
- @property
- def sequences(self):
- """Target database must support SEQUENCEs."""
-
- return exclusions.only_if(
- [lambda config: config.db.dialect.supports_sequences],
- "no sequence support",
- )
-
- @property
- def foreign_key_match(self):
- return exclusions.open()
-
- @property
- def foreign_key_constraint_reflection(self):
- return exclusions.open()
-
- @property
- def check_constraints_w_enforcement(self):
- """Target database must support check constraints
- and also enforce them."""
-
- return exclusions.open()
-
- @property
- def reflects_pk_names(self):
- return exclusions.closed()
-
- @property
- def reflects_fk_options(self):
- return exclusions.closed()
-
- @property
- def sqlalchemy_1x(self):
- return exclusions.skip_if(
- lambda config: util.sqla_2,
- "SQLAlchemy 1.x test",
- )
-
- @property
- def sqlalchemy_2(self):
- return exclusions.skip_if(
- lambda config: not util.sqla_2,
- "SQLAlchemy 2.x test",
- )
-
- @property
- def asyncio(self):
- def go(config):
- try:
- import greenlet # noqa: F401
- except ImportError:
- return False
- else:
- return True
-
- return exclusions.only_if(go)
-
- @property
- def comments(self):
- return exclusions.only_if(
- lambda config: config.db.dialect.supports_comments
- )
-
- @property
- def alter_column(self):
- return exclusions.open()
-
- @property
- def computed_columns(self):
- return exclusions.closed()
-
- @property
- def computed_columns_warn_no_persisted(self):
- def go(config):
- return hasattr(
- config.db.dialect, "supports_virtual_generated_columns"
- )
-
- return exclusions.only_if("postgresql<18") + exclusions.only_if(go)
-
- @property
- def autoincrement_on_composite_pk(self):
- return exclusions.closed()
-
- @property
- def fk_ondelete_is_reflected(self):
- return exclusions.closed()
-
- @property
- def fk_onupdate_is_reflected(self):
- return exclusions.closed()
-
- @property
- def fk_onupdate(self):
- return exclusions.open()
-
- @property
- def fk_ondelete_restrict(self):
- return exclusions.open()
-
- @property
- def fk_onupdate_restrict(self):
- return exclusions.open()
-
- @property
- def fk_ondelete_noaction(self):
- return exclusions.open()
-
- @property
- def fk_initially(self):
- return exclusions.closed()
-
- @property
- def fk_deferrable(self):
- return exclusions.closed()
-
- @property
- def fk_deferrable_is_reflected(self):
- return exclusions.closed()
-
- @property
- def fk_names(self):
- return self.foreign_key_name_reflection
-
- @property
- def foreign_key_name_reflection(self):
- return exclusions.open()
-
- @property
- def integer_subtype_comparisons(self):
- return exclusions.open()
-
- @property
- def no_name_normalize(self):
- return exclusions.skip_if(
- lambda config: config.db.dialect.requires_name_normalize
- )
-
- @property
- def identity_columns(self):
- return exclusions.closed()
-
- @property
- def identity_columns_alter(self):
- return exclusions.closed()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/schemacompare.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/schemacompare.py
deleted file mode 100644
index 204cc4d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/schemacompare.py
+++ /dev/null
@@ -1,169 +0,0 @@
-from itertools import zip_longest
-
-from sqlalchemy import schema
-from sqlalchemy.sql.elements import ClauseList
-
-
-class CompareTable:
- def __init__(self, table):
- self.table = table
-
- def __eq__(self, other):
- if self.table.name != other.name or self.table.schema != other.schema:
- return False
-
- for c1, c2 in zip_longest(self.table.c, other.c):
- if (c1 is None and c2 is not None) or (
- c2 is None and c1 is not None
- ):
- return False
- if CompareColumn(c1) != c2:
- return False
-
- return True
-
- # TODO: compare constraints, indexes
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class CompareColumn:
- def __init__(self, column):
- self.column = column
-
- def __eq__(self, other):
- return (
- self.column.name == other.name
- and self.column.nullable == other.nullable
- )
- # TODO: datatypes etc
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class CompareIndex:
- def __init__(self, index, name_only=False):
- self.index = index
- self.name_only = name_only
-
- def __eq__(self, other):
- if self.name_only:
- return self.index.name == other.name
- else:
- return (
- str(schema.CreateIndex(self.index))
- == str(schema.CreateIndex(other))
- and self.index.dialect_kwargs == other.dialect_kwargs
- )
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- expr = ClauseList(*self.index.expressions)
- try:
- expr_str = expr.compile().string
- except Exception:
- expr_str = str(expr)
- return f""
-
-
-class CompareCheckConstraint:
- def __init__(self, constraint):
- self.constraint = constraint
-
- def __eq__(self, other):
- return (
- isinstance(other, schema.CheckConstraint)
- and self.constraint.name == other.name
- and (str(self.constraint.sqltext) == str(other.sqltext))
- and (other.table.name == self.constraint.table.name)
- and other.table.schema == self.constraint.table.schema
- )
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class CompareForeignKey:
- def __init__(self, constraint):
- self.constraint = constraint
-
- def __eq__(self, other):
- r1 = (
- isinstance(other, schema.ForeignKeyConstraint)
- and self.constraint.name == other.name
- and (other.table.name == self.constraint.table.name)
- and other.table.schema == self.constraint.table.schema
- )
- if not r1:
- return False
- for c1, c2 in zip_longest(self.constraint.columns, other.columns):
- if (c1 is None and c2 is not None) or (
- c2 is None and c1 is not None
- ):
- return False
- if CompareColumn(c1) != c2:
- return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class ComparePrimaryKey:
- def __init__(self, constraint):
- self.constraint = constraint
-
- def __eq__(self, other):
- r1 = (
- isinstance(other, schema.PrimaryKeyConstraint)
- and self.constraint.name == other.name
- and (other.table.name == self.constraint.table.name)
- and other.table.schema == self.constraint.table.schema
- )
- if not r1:
- return False
-
- for c1, c2 in zip_longest(self.constraint.columns, other.columns):
- if (c1 is None and c2 is not None) or (
- c2 is None and c1 is not None
- ):
- return False
- if CompareColumn(c1) != c2:
- return False
-
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class CompareUniqueConstraint:
- def __init__(self, constraint):
- self.constraint = constraint
-
- def __eq__(self, other):
- r1 = (
- isinstance(other, schema.UniqueConstraint)
- and self.constraint.name == other.name
- and (other.table.name == self.constraint.table.name)
- and other.table.schema == self.constraint.table.schema
- )
- if not r1:
- return False
-
- for c1, c2 in zip_longest(self.constraint.columns, other.columns):
- if (c1 is None and c2 is not None) or (
- c2 is None and c1 is not None
- ):
- return False
- if CompareColumn(c1) != c2:
- return False
-
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py
deleted file mode 100644
index 3da498d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .test_autogen_comments import * # noqa
-from .test_autogen_computed import * # noqa
-from .test_autogen_diffs import * # noqa
-from .test_autogen_fks import * # noqa
-from .test_autogen_identity import * # noqa
-from .test_environment import * # noqa
-from .test_op import * # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 07865af..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc
deleted file mode 100644
index 780467b..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc
deleted file mode 100644
index a926364..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc
deleted file mode 100644
index 00b1ec2..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc
deleted file mode 100644
index 9b684a1..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc
deleted file mode 100644
index bcb255b..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc
deleted file mode 100644
index ac388d8..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc
deleted file mode 100644
index fe8f887..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc
deleted file mode 100644
index 608b25b..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py
deleted file mode 100644
index 8329a1a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py
+++ /dev/null
@@ -1,479 +0,0 @@
-from __future__ import annotations
-
-from typing import Any
-from typing import Dict
-from typing import Literal
-from typing import overload
-from typing import Set
-
-from sqlalchemy import CHAR
-from sqlalchemy import CheckConstraint
-from sqlalchemy import Column
-from sqlalchemy import event
-from sqlalchemy import ForeignKey
-from sqlalchemy import Index
-from sqlalchemy import inspect
-from sqlalchemy import Integer
-from sqlalchemy import MetaData
-from sqlalchemy import Numeric
-from sqlalchemy import PrimaryKeyConstraint
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy import Text
-from sqlalchemy import text
-from sqlalchemy import UniqueConstraint
-
-from ... import autogenerate
-from ... import util
-from ...autogenerate import api
-from ...ddl.base import _fk_spec
-from ...migration import MigrationContext
-from ...operations import ops
-from ...testing import config
-from ...testing import eq_
-from ...testing.env import clear_staging_env
-from ...testing.env import staging_env
-
-names_in_this_test: Set[Any] = set()
-
-
-@event.listens_for(Table, "after_parent_attach")
-def new_table(table, parent):
- names_in_this_test.add(table.name)
-
-
-def _default_include_object(obj, name, type_, reflected, compare_to):
- if type_ == "table":
- return name in names_in_this_test
- else:
- return True
-
-
-_default_object_filters: Any = _default_include_object
-
-_default_name_filters: Any = None
-
-
-class ModelOne:
- __requires__ = ("unique_constraint_reflection",)
-
- schema: Any = None
-
- @classmethod
- def _get_db_schema(cls):
- schema = cls.schema
-
- m = MetaData(schema=schema)
-
- Table(
- "user",
- m,
- Column("id", Integer, primary_key=True),
- Column("name", String(50)),
- Column("a1", Text),
- Column("pw", String(50)),
- Index("pw_idx", "pw"),
- )
-
- Table(
- "address",
- m,
- Column("id", Integer, primary_key=True),
- Column("email_address", String(100), nullable=False),
- )
-
- Table(
- "order",
- m,
- Column("order_id", Integer, primary_key=True),
- Column(
- "amount",
- Numeric(8, 2),
- nullable=False,
- server_default=text("0"),
- ),
- CheckConstraint("amount >= 0", name="ck_order_amount"),
- )
-
- Table(
- "extra",
- m,
- Column("x", CHAR),
- Column("uid", Integer, ForeignKey("user.id")),
- )
-
- return m
-
- @classmethod
- def _get_model_schema(cls):
- schema = cls.schema
-
- m = MetaData(schema=schema)
-
- Table(
- "user",
- m,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", Text, server_default="x"),
- )
-
- Table(
- "address",
- m,
- Column("id", Integer, primary_key=True),
- Column("email_address", String(100), nullable=False),
- Column("street", String(50)),
- UniqueConstraint("email_address", name="uq_email"),
- )
-
- Table(
- "order",
- m,
- Column("order_id", Integer, primary_key=True),
- Column(
- "amount",
- Numeric(10, 2),
- nullable=True,
- server_default=text("0"),
- ),
- Column("user_id", Integer, ForeignKey("user.id")),
- CheckConstraint("amount > -1", name="ck_order_amount"),
- )
-
- Table(
- "item",
- m,
- Column("id", Integer, primary_key=True),
- Column("description", String(100)),
- Column("order_id", Integer, ForeignKey("order.order_id")),
- CheckConstraint("len(description) > 5"),
- )
- return m
-
-
-class NamingConvModel:
- __requires__ = ("unique_constraint_reflection",)
- configure_opts = {"conv_all_constraint_names": True}
- naming_convention = {
- "ix": "ix_%(column_0_label)s",
- "uq": "uq_%(table_name)s_%(constraint_name)s",
- "ck": "ck_%(table_name)s_%(constraint_name)s",
- "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
- "pk": "pk_%(table_name)s",
- }
-
- @classmethod
- def _get_db_schema(cls):
- # database side - assume all constraints have a name that
- # we would assume here is a "db generated" name. need to make
- # sure these all render with op.f().
- m = MetaData()
- Table(
- "x1",
- m,
- Column("q", Integer),
- Index("db_x1_index_q", "q"),
- PrimaryKeyConstraint("q", name="db_x1_primary_q"),
- )
- Table(
- "x2",
- m,
- Column("q", Integer),
- Column("p", ForeignKey("x1.q", name="db_x2_foreign_q")),
- CheckConstraint("q > 5", name="db_x2_check_q"),
- )
- Table(
- "x3",
- m,
- Column("q", Integer),
- Column("r", Integer),
- Column("s", Integer),
- UniqueConstraint("q", name="db_x3_unique_q"),
- )
- Table(
- "x4",
- m,
- Column("q", Integer),
- PrimaryKeyConstraint("q", name="db_x4_primary_q"),
- )
- Table(
- "x5",
- m,
- Column("q", Integer),
- Column("p", ForeignKey("x4.q", name="db_x5_foreign_q")),
- Column("r", Integer),
- Column("s", Integer),
- PrimaryKeyConstraint("q", name="db_x5_primary_q"),
- UniqueConstraint("r", name="db_x5_unique_r"),
- CheckConstraint("s > 5", name="db_x5_check_s"),
- )
- # SQLite and it's "no names needed" thing. bleh.
- # we can't have a name for these so you'll see "None" for the name.
- Table(
- "unnamed_sqlite",
- m,
- Column("q", Integer),
- Column("r", Integer),
- PrimaryKeyConstraint("q"),
- UniqueConstraint("r"),
- )
- return m
-
- @classmethod
- def _get_model_schema(cls):
- from sqlalchemy.sql.naming import conv
-
- m = MetaData(naming_convention=cls.naming_convention)
- Table(
- "x1", m, Column("q", Integer, primary_key=True), Index(None, "q")
- )
- Table(
- "x2",
- m,
- Column("q", Integer),
- Column("p", ForeignKey("x1.q")),
- CheckConstraint("q > 5", name="token_x2check1"),
- )
- Table(
- "x3",
- m,
- Column("q", Integer),
- Column("r", Integer),
- Column("s", Integer),
- UniqueConstraint("r", name="token_x3r"),
- UniqueConstraint("s", name=conv("userdef_x3_unique_s")),
- )
- Table(
- "x4",
- m,
- Column("q", Integer, primary_key=True),
- Index("userdef_x4_idx_q", "q"),
- )
- Table(
- "x6",
- m,
- Column("q", Integer, primary_key=True),
- Column("p", ForeignKey("x4.q")),
- Column("r", Integer),
- Column("s", Integer),
- UniqueConstraint("r", name="token_x6r"),
- CheckConstraint("s > 5", "token_x6check1"),
- CheckConstraint("s < 20", conv("userdef_x6_check_s")),
- )
- return m
-
-
-class _ComparesFKs:
- def _assert_fk_diff(
- self,
- diff,
- type_,
- source_table,
- source_columns,
- target_table,
- target_columns,
- name=None,
- conditional_name=None,
- source_schema=None,
- onupdate=None,
- ondelete=None,
- initially=None,
- deferrable=None,
- ):
- # the public API for ForeignKeyConstraint was not very rich
- # in 0.7, 0.8, so here we use the well-known but slightly
- # private API to get at its elements
- (
- fk_source_schema,
- fk_source_table,
- fk_source_columns,
- fk_target_schema,
- fk_target_table,
- fk_target_columns,
- fk_onupdate,
- fk_ondelete,
- fk_deferrable,
- fk_initially,
- ) = _fk_spec(diff[1])
-
- eq_(diff[0], type_)
- eq_(fk_source_table, source_table)
- eq_(fk_source_columns, source_columns)
- eq_(fk_target_table, target_table)
- eq_(fk_source_schema, source_schema)
- eq_(fk_onupdate, onupdate)
- eq_(fk_ondelete, ondelete)
- eq_(fk_initially, initially)
- eq_(fk_deferrable, deferrable)
-
- eq_([elem.column.name for elem in diff[1].elements], target_columns)
- if conditional_name is not None:
- if conditional_name == "servergenerated":
- fks = inspect(self.bind).get_foreign_keys(source_table)
- server_fk_name = fks[0]["name"]
- eq_(diff[1].name, server_fk_name)
- else:
- eq_(diff[1].name, conditional_name)
- else:
- eq_(diff[1].name, name)
-
-
-class AutogenTest(_ComparesFKs):
- def _flatten_diffs(self, diffs):
- for d in diffs:
- if isinstance(d, list):
- yield from self._flatten_diffs(d)
- else:
- yield d
-
- @classmethod
- def _get_bind(cls):
- return config.db
-
- configure_opts: Dict[Any, Any] = {}
-
- @classmethod
- def setup_class(cls):
- staging_env()
- cls.bind = cls._get_bind()
- cls.m1 = cls._get_db_schema()
- cls.m1.create_all(cls.bind)
- cls.m2 = cls._get_model_schema()
-
- @classmethod
- def teardown_class(cls):
- cls.m1.drop_all(cls.bind)
- clear_staging_env()
-
- def setUp(self):
- self.conn = conn = self.bind.connect()
- ctx_opts = {
- "compare_type": True,
- "compare_server_default": True,
- "target_metadata": self.m2,
- "upgrade_token": "upgrades",
- "downgrade_token": "downgrades",
- "alembic_module_prefix": "op.",
- "sqlalchemy_module_prefix": "sa.",
- "include_object": _default_object_filters,
- "include_name": _default_name_filters,
- }
- if self.configure_opts:
- ctx_opts.update(self.configure_opts)
- self.context = context = MigrationContext.configure(
- connection=conn, opts=ctx_opts
- )
-
- self.autogen_context = api.AutogenContext(context, self.m2)
-
- def tearDown(self):
- self.conn.close()
-
- def _update_context(
- self, object_filters=None, name_filters=None, include_schemas=None
- ):
- if include_schemas is not None:
- self.autogen_context.opts["include_schemas"] = include_schemas
- if object_filters is not None:
- self.autogen_context._object_filters = [object_filters]
- if name_filters is not None:
- self.autogen_context._name_filters = [name_filters]
- return self.autogen_context
-
-
-class AutogenFixtureTest(_ComparesFKs):
-
- @overload
- def _fixture(
- self,
- m1: MetaData,
- m2: MetaData,
- include_schemas=...,
- opts=...,
- object_filters=...,
- name_filters=...,
- *,
- return_ops: Literal[True],
- max_identifier_length=...,
- ) -> ops.UpgradeOps: ...
-
- @overload
- def _fixture(
- self,
- m1: MetaData,
- m2: MetaData,
- include_schemas=...,
- opts=...,
- object_filters=...,
- name_filters=...,
- *,
- return_ops: Literal[False] = ...,
- max_identifier_length=...,
- ) -> list[Any]: ...
-
- def _fixture(
- self,
- m1: MetaData,
- m2: MetaData,
- include_schemas=False,
- opts=None,
- object_filters=_default_object_filters,
- name_filters=_default_name_filters,
- return_ops: bool = False,
- max_identifier_length=None,
- ) -> ops.UpgradeOps | list[Any]:
- if max_identifier_length:
- dialect = self.bind.dialect
- existing_length = dialect.max_identifier_length
- dialect.max_identifier_length = (
- dialect._user_defined_max_identifier_length
- ) = max_identifier_length
- try:
- self._alembic_metadata, model_metadata = m1, m2
- for m in util.to_list(self._alembic_metadata):
- m.create_all(self.bind)
-
- with self.bind.connect() as conn:
- ctx_opts = {
- "compare_type": True,
- "compare_server_default": True,
- "target_metadata": model_metadata,
- "upgrade_token": "upgrades",
- "downgrade_token": "downgrades",
- "alembic_module_prefix": "op.",
- "sqlalchemy_module_prefix": "sa.",
- "include_object": object_filters,
- "include_name": name_filters,
- "include_schemas": include_schemas,
- }
- if opts:
- ctx_opts.update(opts)
- self.context = context = MigrationContext.configure(
- connection=conn, opts=ctx_opts
- )
-
- autogen_context = api.AutogenContext(context, model_metadata)
- uo = ops.UpgradeOps(ops=[])
- autogenerate._produce_net_changes(autogen_context, uo)
-
- if return_ops:
- return uo
- else:
- return uo.as_diffs()
- finally:
- if max_identifier_length:
- dialect = self.bind.dialect
- dialect.max_identifier_length = (
- dialect._user_defined_max_identifier_length
- ) = existing_length
-
- def setUp(self):
- staging_env()
- self.bind = config.db
-
- def tearDown(self):
- if hasattr(self, "_alembic_metadata"):
- for m in util.to_list(self._alembic_metadata):
- m.drop_all(self.bind)
- clear_staging_env()
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py
deleted file mode 100644
index 7ef074f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py
+++ /dev/null
@@ -1,242 +0,0 @@
-from sqlalchemy import Column
-from sqlalchemy import Float
-from sqlalchemy import MetaData
-from sqlalchemy import String
-from sqlalchemy import Table
-
-from ._autogen_fixtures import AutogenFixtureTest
-from ...testing import eq_
-from ...testing import mock
-from ...testing import TestBase
-
-
-class AutogenerateCommentsTest(AutogenFixtureTest, TestBase):
- __backend__ = True
-
- __requires__ = ("comments",)
-
- def test_existing_table_comment_no_change(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- comment="this is some table",
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- comment="this is some table",
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
- def test_add_table_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("some_table", m1, Column("test", String(10), primary_key=True))
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- comment="this is some table",
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "add_table_comment")
- eq_(diffs[0][1].comment, "this is some table")
- eq_(diffs[0][2], None)
-
- def test_remove_table_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- comment="this is some table",
- )
-
- Table("some_table", m2, Column("test", String(10), primary_key=True))
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "remove_table_comment")
- eq_(diffs[0][1].comment, None)
-
- def test_alter_table_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- comment="this is some table",
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- comment="this is also some table",
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "add_table_comment")
- eq_(diffs[0][1].comment, "this is also some table")
- eq_(diffs[0][2], "this is some table")
-
- def test_existing_column_comment_no_change(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the amount"),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the amount"),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
- def test_add_column_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- Column("amount", Float),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the amount"),
- )
-
- diffs = self._fixture(m1, m2)
- eq_(
- diffs,
- [
- [
- (
- "modify_comment",
- None,
- "some_table",
- "amount",
- {
- "existing_nullable": True,
- "existing_type": mock.ANY,
- "existing_server_default": False,
- },
- None,
- "the amount",
- )
- ]
- ],
- )
-
- def test_remove_column_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the amount"),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- Column("amount", Float),
- )
-
- diffs = self._fixture(m1, m2)
- eq_(
- diffs,
- [
- [
- (
- "modify_comment",
- None,
- "some_table",
- "amount",
- {
- "existing_nullable": True,
- "existing_type": mock.ANY,
- "existing_server_default": False,
- },
- "the amount",
- None,
- )
- ]
- ],
- )
-
- def test_alter_column_comment(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the amount"),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- Column("amount", Float, comment="the adjusted amount"),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(
- diffs,
- [
- [
- (
- "modify_comment",
- None,
- "some_table",
- "amount",
- {
- "existing_nullable": True,
- "existing_type": mock.ANY,
- "existing_server_default": False,
- },
- "the amount",
- "the adjusted amount",
- )
- ]
- ],
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py
deleted file mode 100644
index 586691b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py
+++ /dev/null
@@ -1,157 +0,0 @@
-from contextlib import nullcontext
-
-import sqlalchemy as sa
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import MetaData
-from sqlalchemy import Table
-
-from ._autogen_fixtures import AutogenFixtureTest
-from ... import testing
-from ...testing import config
-from ...testing import eq_
-from ...testing import expect_warnings
-from ...testing import is_
-from ...testing import is_true
-from ...testing import mock
-from ...testing import TestBase
-
-
-class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
- __requires__ = ("computed_columns",)
- __backend__ = True
-
- def _fixture_ctx(self):
- if config.requirements.computed_columns_warn_no_persisted.enabled:
- ctx = expect_warnings()
- else:
- ctx = nullcontext()
- return ctx
-
- def test_add_computed_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("user", m1, Column("id", Integer, primary_key=True))
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("foo", Integer, sa.Computed("5")),
- )
-
- with self._fixture_ctx():
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "add_column")
- eq_(diffs[0][2], "user")
- eq_(diffs[0][3].name, "foo")
- c = diffs[0][3].computed
-
- is_true(isinstance(c, sa.Computed))
- is_(c.persisted, None)
- eq_(str(c.sqltext), "5")
-
- def test_remove_computed_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("foo", Integer, sa.Computed("5")),
- )
-
- Table("user", m2, Column("id", Integer, primary_key=True))
-
- with self._fixture_ctx():
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "remove_column")
- eq_(diffs[0][2], "user")
- c = diffs[0][3]
- eq_(c.name, "foo")
-
- is_true(isinstance(c.computed, sa.Computed))
- is_true(isinstance(c.server_default, sa.Computed))
-
- @testing.combinations(
- lambda: (None, sa.Computed("bar*5")),
- (lambda: (sa.Computed("bar*5"), None)),
- lambda: (
- sa.Computed("bar*5"),
- sa.Computed("bar * 42", persisted=True),
- ),
- lambda: (sa.Computed("bar*5"), sa.Computed("bar * 42")),
- )
- def test_cant_change_computed_warning(self, test_case):
- arg_before, arg_after = testing.resolve_lambda(test_case, **locals())
- m1 = MetaData()
- m2 = MetaData()
-
- arg_before = [] if arg_before is None else [arg_before]
- arg_after = [] if arg_after is None else [arg_after]
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("bar", Integer),
- Column("foo", Integer, *arg_before),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("bar", Integer),
- Column("foo", Integer, *arg_after),
- )
-
- with mock.patch("alembic.util.warn") as mock_warn, self._fixture_ctx():
- diffs = self._fixture(m1, m2)
-
- eq_(
- mock_warn.mock_calls,
- [mock.call("Computed default on user.foo cannot be modified")],
- )
-
- eq_(list(diffs), [])
-
- @testing.combinations(
- lambda: (None, None),
- lambda: (sa.Computed("5"), sa.Computed("5")),
- lambda: (sa.Computed("bar*5"), sa.Computed("bar*5")),
- lambda: (sa.Computed("bar*5"), sa.Computed("bar * \r\n\t5")),
- )
- def test_computed_unchanged(self, test_case):
- arg_before, arg_after = testing.resolve_lambda(test_case, **locals())
- m1 = MetaData()
- m2 = MetaData()
-
- arg_before = [] if arg_before is None else [arg_before]
- arg_after = [] if arg_after is None else [arg_after]
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("bar", Integer),
- Column("foo", Integer, *arg_before),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("bar", Integer),
- Column("foo", Integer, *arg_after),
- )
-
- with mock.patch("alembic.util.warn") as mock_warn, self._fixture_ctx():
- diffs = self._fixture(m1, m2)
- eq_(mock_warn.mock_calls, [])
-
- eq_(list(diffs), [])
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py
deleted file mode 100644
index 75bcd37..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py
+++ /dev/null
@@ -1,273 +0,0 @@
-from sqlalchemy import BigInteger
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import MetaData
-from sqlalchemy import Table
-from sqlalchemy.testing import in_
-
-from ._autogen_fixtures import AutogenFixtureTest
-from ... import testing
-from ...testing import config
-from ...testing import eq_
-from ...testing import is_
-from ...testing import TestBase
-
-
-class AlterColumnTest(AutogenFixtureTest, TestBase):
- __backend__ = True
-
- @testing.combinations((True,), (False,))
- @config.requirements.comments
- def test_all_existings_filled(self, pk):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("a", m1, Column("x", Integer, primary_key=pk))
- Table("a", m2, Column("x", Integer, comment="x", primary_key=pk))
-
- alter_col = self._assert_alter_col(m1, m2, pk)
- eq_(alter_col.modify_comment, "x")
-
- @testing.combinations((True,), (False,))
- @config.requirements.comments
- def test_all_existings_filled_in_notnull(self, pk):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("a", m1, Column("x", Integer, nullable=False, primary_key=pk))
- Table(
- "a",
- m2,
- Column("x", Integer, nullable=False, comment="x", primary_key=pk),
- )
-
- self._assert_alter_col(m1, m2, pk, nullable=False)
-
- @testing.combinations((True,), (False,))
- @config.requirements.comments
- def test_all_existings_filled_in_comment(self, pk):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("a", m1, Column("x", Integer, comment="old", primary_key=pk))
- Table("a", m2, Column("x", Integer, comment="new", primary_key=pk))
-
- alter_col = self._assert_alter_col(m1, m2, pk)
- eq_(alter_col.existing_comment, "old")
-
- @testing.combinations((True,), (False,))
- @config.requirements.comments
- def test_all_existings_filled_in_server_default(self, pk):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a", m1, Column("x", Integer, server_default="5", primary_key=pk)
- )
- Table(
- "a",
- m2,
- Column(
- "x", Integer, server_default="5", comment="new", primary_key=pk
- ),
- )
-
- alter_col = self._assert_alter_col(m1, m2, pk)
- in_("5", alter_col.existing_server_default.arg.text)
-
- def _assert_alter_col(self, m1, m2, pk, nullable=None):
- ops = self._fixture(m1, m2, return_ops=True)
- modify_table = ops.ops[-1]
- alter_col = modify_table.ops[0]
-
- if nullable is None:
- eq_(alter_col.existing_nullable, not pk)
- else:
- eq_(alter_col.existing_nullable, nullable)
- assert alter_col.existing_type._compare_type_affinity(Integer())
- return alter_col
-
-
-class AutoincrementTest(AutogenFixtureTest, TestBase):
- __backend__ = True
- __requires__ = ("integer_subtype_comparisons",)
-
- def test_alter_column_autoincrement_none(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("a", m1, Column("x", Integer, nullable=False))
- Table("a", m2, Column("x", Integer, nullable=True))
-
- ops = self._fixture(m1, m2, return_ops=True)
- assert "autoincrement" not in ops.ops[0].ops[0].kw
-
- def test_alter_column_autoincrement_pk_false(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("x", Integer, primary_key=True, autoincrement=False),
- )
- Table(
- "a",
- m2,
- Column("x", BigInteger, primary_key=True, autoincrement=False),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], False)
-
- def test_alter_column_autoincrement_pk_implicit_true(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("a", m1, Column("x", Integer, primary_key=True))
- Table("a", m2, Column("x", BigInteger, primary_key=True))
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], True)
-
- def test_alter_column_autoincrement_pk_explicit_true(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a", m1, Column("x", Integer, primary_key=True, autoincrement=True)
- )
- Table(
- "a",
- m2,
- Column("x", BigInteger, primary_key=True, autoincrement=True),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], True)
-
- def test_alter_column_autoincrement_nonpk_false(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True),
- Column("x", Integer, autoincrement=False),
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True),
- Column("x", BigInteger, autoincrement=False),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], False)
-
- def test_alter_column_autoincrement_nonpk_implicit_false(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True),
- Column("x", Integer),
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True),
- Column("x", BigInteger),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- assert "autoincrement" not in ops.ops[0].ops[0].kw
-
- def test_alter_column_autoincrement_nonpk_explicit_true(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True, autoincrement=False),
- Column("x", Integer, autoincrement=True),
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True, autoincrement=False),
- Column("x", BigInteger, autoincrement=True),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], True)
-
- def test_alter_column_autoincrement_compositepk_false(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True),
- Column("x", Integer, primary_key=True, autoincrement=False),
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True),
- Column("x", BigInteger, primary_key=True, autoincrement=False),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], False)
-
- def test_alter_column_autoincrement_compositepk_implicit_false(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True),
- Column("x", Integer, primary_key=True),
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True),
- Column("x", BigInteger, primary_key=True),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- assert "autoincrement" not in ops.ops[0].ops[0].kw
-
- @config.requirements.autoincrement_on_composite_pk
- def test_alter_column_autoincrement_compositepk_explicit_true(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "a",
- m1,
- Column("id", Integer, primary_key=True, autoincrement=False),
- Column("x", Integer, primary_key=True, autoincrement=True),
- # on SQLA 1.0 and earlier, this being present
- # trips the "add KEY for the primary key" so that the
- # AUTO_INCREMENT keyword is accepted by MySQL. SQLA 1.1 and
- # greater the columns are just reorganized.
- mysql_engine="InnoDB",
- )
- Table(
- "a",
- m2,
- Column("id", Integer, primary_key=True, autoincrement=False),
- Column("x", BigInteger, primary_key=True, autoincrement=True),
- )
-
- ops = self._fixture(m1, m2, return_ops=True)
- is_(ops.ops[0].ops[0].kw["autoincrement"], True)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py
deleted file mode 100644
index d69736e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py
+++ /dev/null
@@ -1,1191 +0,0 @@
-from sqlalchemy import Column
-from sqlalchemy import ForeignKeyConstraint
-from sqlalchemy import Integer
-from sqlalchemy import MetaData
-from sqlalchemy import String
-from sqlalchemy import Table
-
-from ._autogen_fixtures import AutogenFixtureTest
-from ...testing import combinations
-from ...testing import config
-from ...testing import eq_
-from ...testing import mock
-from ...testing import TestBase
-
-
-class AutogenerateForeignKeysTest(AutogenFixtureTest, TestBase):
- __backend__ = True
- __requires__ = ("foreign_key_constraint_reflection",)
-
- def test_remove_fk(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- ForeignKeyConstraint(["test2"], ["some_table.test"]),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- )
-
- diffs = self._fixture(m1, m2)
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["test2"],
- "some_table",
- ["test"],
- conditional_name="servergenerated",
- )
-
- def test_add_fk(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- )
-
- Table(
- "some_table",
- m2,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- ForeignKeyConstraint(["test2"], ["some_table.test"]),
- )
-
- diffs = self._fixture(m1, m2)
-
- self._assert_fk_diff(
- diffs[0], "add_fk", "user", ["test2"], "some_table", ["test"]
- )
-
- def test_no_change(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", Integer),
- ForeignKeyConstraint(["test2"], ["some_table.id"]),
- )
-
- Table(
- "some_table",
- m2,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", Integer),
- ForeignKeyConstraint(["test2"], ["some_table.id"]),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
- def test_no_change_composite_fk(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- ForeignKeyConstraint(
- ["other_id_1", "other_id_2"],
- ["some_table.id_1", "some_table.id_2"],
- ),
- )
-
- Table(
- "some_table",
- m2,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- ForeignKeyConstraint(
- ["other_id_1", "other_id_2"],
- ["some_table.id_1", "some_table.id_2"],
- ),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
- @config.requirements.foreign_key_name_reflection
- def test_casing_convention_changed_so_put_drops_first(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("test", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- ForeignKeyConstraint(["test2"], ["some_table.test"], name="MyFK"),
- )
-
- Table(
- "some_table",
- m2,
- Column("test", String(10), primary_key=True),
- )
-
- # foreign key autogen currently does not take "name" into account,
- # so change the def just for the purposes of testing the
- # add/drop order for now.
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("test2", String(10)),
- ForeignKeyConstraint(["a1"], ["some_table.test"], name="myfk"),
- )
-
- diffs = self._fixture(m1, m2)
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["test2"],
- "some_table",
- ["test"],
- name="MyFK",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["a1"],
- "some_table",
- ["test"],
- name="myfk",
- )
-
- def test_add_composite_fk_with_name(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- )
-
- Table(
- "some_table",
- m2,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- ForeignKeyConstraint(
- ["other_id_1", "other_id_2"],
- ["some_table.id_1", "some_table.id_2"],
- name="fk_test_name",
- ),
- )
-
- diffs = self._fixture(m1, m2)
- self._assert_fk_diff(
- diffs[0],
- "add_fk",
- "user",
- ["other_id_1", "other_id_2"],
- "some_table",
- ["id_1", "id_2"],
- name="fk_test_name",
- )
-
- @config.requirements.no_name_normalize
- def test_remove_composite_fk(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- ForeignKeyConstraint(
- ["other_id_1", "other_id_2"],
- ["some_table.id_1", "some_table.id_2"],
- name="fk_test_name",
- ),
- )
-
- Table(
- "some_table",
- m2,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("a1", String(10), server_default="x"),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- )
-
- diffs = self._fixture(m1, m2)
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["other_id_1", "other_id_2"],
- "some_table",
- ["id_1", "id_2"],
- conditional_name="fk_test_name",
- )
-
- def test_add_fk_colkeys(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- )
-
- Table(
- "some_table",
- m2,
- Column("id_1", String(10), key="tid1", primary_key=True),
- Column("id_2", String(10), key="tid2", primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("other_id_1", String(10), key="oid1"),
- Column("other_id_2", String(10), key="oid2"),
- ForeignKeyConstraint(
- ["oid1", "oid2"],
- ["some_table.tid1", "some_table.tid2"],
- name="fk_test_name",
- ),
- )
-
- diffs = self._fixture(m1, m2)
-
- self._assert_fk_diff(
- diffs[0],
- "add_fk",
- "user",
- ["other_id_1", "other_id_2"],
- "some_table",
- ["id_1", "id_2"],
- name="fk_test_name",
- )
-
- def test_no_change_colkeys(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id_1", String(10), primary_key=True),
- Column("id_2", String(10), primary_key=True),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("other_id_1", String(10)),
- Column("other_id_2", String(10)),
- ForeignKeyConstraint(
- ["other_id_1", "other_id_2"],
- ["some_table.id_1", "some_table.id_2"],
- ),
- )
-
- Table(
- "some_table",
- m2,
- Column("id_1", String(10), key="tid1", primary_key=True),
- Column("id_2", String(10), key="tid2", primary_key=True),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("other_id_1", String(10), key="oid1"),
- Column("other_id_2", String(10), key="oid2"),
- ForeignKeyConstraint(
- ["oid1", "oid2"], ["some_table.tid1", "some_table.tid2"]
- ),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
-
-class IncludeHooksTest(AutogenFixtureTest, TestBase):
- __backend__ = True
- __requires__ = ("fk_names",)
-
- @combinations(("object",), ("name",))
- @config.requirements.no_name_normalize
- def test_remove_connection_fk(self, hook_type):
- m1 = MetaData()
- m2 = MetaData()
-
- ref = Table(
- "ref",
- m1,
- Column("id", Integer, primary_key=True),
- )
- t1 = Table(
- "t",
- m1,
- Column("x", Integer),
- Column("y", Integer),
- )
- t1.append_constraint(
- ForeignKeyConstraint([t1.c.x], [ref.c.id], name="fk1")
- )
- t1.append_constraint(
- ForeignKeyConstraint([t1.c.y], [ref.c.id], name="fk2")
- )
-
- ref = Table(
- "ref",
- m2,
- Column("id", Integer, primary_key=True),
- )
- Table(
- "t",
- m2,
- Column("x", Integer),
- Column("y", Integer),
- )
-
- if hook_type == "object":
-
- def include_object(object_, name, type_, reflected, compare_to):
- return not (
- isinstance(object_, ForeignKeyConstraint)
- and type_ == "foreign_key_constraint"
- and reflected
- and name == "fk1"
- )
-
- diffs = self._fixture(m1, m2, object_filters=include_object)
- elif hook_type == "name":
-
- def include_name(name, type_, parent_names):
- if name == "fk1":
- if type_ == "index": # MariaDB thing
- return True
- eq_(type_, "foreign_key_constraint")
- eq_(
- parent_names,
- {
- "schema_name": None,
- "table_name": "t",
- "schema_qualified_table_name": "t",
- },
- )
- return False
- else:
- return True
-
- diffs = self._fixture(m1, m2, name_filters=include_name)
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "t",
- ["y"],
- "ref",
- ["id"],
- conditional_name="fk2",
- )
- eq_(len(diffs), 1)
-
- def test_add_metadata_fk(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "ref",
- m1,
- Column("id", Integer, primary_key=True),
- )
- Table(
- "t",
- m1,
- Column("x", Integer),
- Column("y", Integer),
- )
-
- ref = Table(
- "ref",
- m2,
- Column("id", Integer, primary_key=True),
- )
- t2 = Table(
- "t",
- m2,
- Column("x", Integer),
- Column("y", Integer),
- )
- t2.append_constraint(
- ForeignKeyConstraint([t2.c.x], [ref.c.id], name="fk1")
- )
- t2.append_constraint(
- ForeignKeyConstraint([t2.c.y], [ref.c.id], name="fk2")
- )
-
- def include_object(object_, name, type_, reflected, compare_to):
- return not (
- isinstance(object_, ForeignKeyConstraint)
- and type_ == "foreign_key_constraint"
- and not reflected
- and name == "fk1"
- )
-
- diffs = self._fixture(m1, m2, object_filters=include_object)
-
- self._assert_fk_diff(
- diffs[0], "add_fk", "t", ["y"], "ref", ["id"], name="fk2"
- )
- eq_(len(diffs), 1)
-
- @combinations(("object",), ("name",))
- @config.requirements.no_name_normalize
- def test_change_fk(self, hook_type):
- m1 = MetaData()
- m2 = MetaData()
-
- r1a = Table(
- "ref_a",
- m1,
- Column("a", Integer, primary_key=True),
- )
- Table(
- "ref_b",
- m1,
- Column("a", Integer, primary_key=True),
- Column("b", Integer, primary_key=True),
- )
- t1 = Table(
- "t",
- m1,
- Column("x", Integer),
- Column("y", Integer),
- Column("z", Integer),
- )
- t1.append_constraint(
- ForeignKeyConstraint([t1.c.x], [r1a.c.a], name="fk1")
- )
- t1.append_constraint(
- ForeignKeyConstraint([t1.c.y], [r1a.c.a], name="fk2")
- )
-
- Table(
- "ref_a",
- m2,
- Column("a", Integer, primary_key=True),
- )
- r2b = Table(
- "ref_b",
- m2,
- Column("a", Integer, primary_key=True),
- Column("b", Integer, primary_key=True),
- )
- t2 = Table(
- "t",
- m2,
- Column("x", Integer),
- Column("y", Integer),
- Column("z", Integer),
- )
- t2.append_constraint(
- ForeignKeyConstraint(
- [t2.c.x, t2.c.z], [r2b.c.a, r2b.c.b], name="fk1"
- )
- )
- t2.append_constraint(
- ForeignKeyConstraint(
- [t2.c.y, t2.c.z], [r2b.c.a, r2b.c.b], name="fk2"
- )
- )
-
- if hook_type == "object":
-
- def include_object(object_, name, type_, reflected, compare_to):
- return not (
- isinstance(object_, ForeignKeyConstraint)
- and type_ == "foreign_key_constraint"
- and name == "fk1"
- )
-
- diffs = self._fixture(m1, m2, object_filters=include_object)
- elif hook_type == "name":
-
- def include_name(name, type_, parent_names):
- if type_ == "index":
- return True # MariaDB thing
-
- if name == "fk1":
- eq_(type_, "foreign_key_constraint")
- eq_(
- parent_names,
- {
- "schema_name": None,
- "table_name": "t",
- "schema_qualified_table_name": "t",
- },
- )
- return False
- else:
- return True
-
- diffs = self._fixture(m1, m2, name_filters=include_name)
-
- if hook_type == "object":
- self._assert_fk_diff(
- diffs[0], "remove_fk", "t", ["y"], "ref_a", ["a"], name="fk2"
- )
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "t",
- ["y", "z"],
- "ref_b",
- ["a", "b"],
- name="fk2",
- )
- eq_(len(diffs), 2)
- elif hook_type == "name":
- eq_(
- {(d[0], d[1].name) for d in diffs},
- {("add_fk", "fk2"), ("add_fk", "fk1"), ("remove_fk", "fk2")},
- )
-
-
-class AutogenerateFKOptionsTest(AutogenFixtureTest, TestBase):
- __backend__ = True
-
- def _fk_opts_fixture(self, old_opts, new_opts):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "some_table",
- m1,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m1,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("tid", Integer),
- ForeignKeyConstraint(["tid"], ["some_table.id"], **old_opts),
- )
-
- Table(
- "some_table",
- m2,
- Column("id", Integer, primary_key=True),
- Column("test", String(10)),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, primary_key=True),
- Column("name", String(50), nullable=False),
- Column("tid", Integer),
- ForeignKeyConstraint(["tid"], ["some_table.id"], **new_opts),
- )
-
- return self._fixture(m1, m2)
-
- @config.requirements.fk_ondelete_is_reflected
- def test_add_ondelete(self):
- diffs = self._fk_opts_fixture({}, {"ondelete": "cascade"})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- ondelete=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- ondelete="cascade",
- )
-
- @config.requirements.fk_ondelete_is_reflected
- def test_remove_ondelete(self):
- diffs = self._fk_opts_fixture({"ondelete": "CASCADE"}, {})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- ondelete="CASCADE",
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- ondelete=None,
- )
-
- def test_nochange_ondelete(self):
- """test case sensitivity"""
- diffs = self._fk_opts_fixture(
- {"ondelete": "caSCAde"}, {"ondelete": "CasCade"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_onupdate_is_reflected
- def test_add_onupdate(self):
- diffs = self._fk_opts_fixture({}, {"onupdate": "cascade"})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate="cascade",
- )
-
- @config.requirements.fk_onupdate_is_reflected
- def test_remove_onupdate(self):
- diffs = self._fk_opts_fixture({"onupdate": "CASCADE"}, {})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate="CASCADE",
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate=None,
- )
-
- @config.requirements.fk_onupdate
- def test_nochange_onupdate(self):
- """test case sensitivity"""
- diffs = self._fk_opts_fixture(
- {"onupdate": "caSCAde"}, {"onupdate": "CasCade"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_ondelete_restrict
- def test_nochange_ondelete_restrict(self):
- """test the RESTRICT option which MySQL doesn't report on"""
-
- diffs = self._fk_opts_fixture(
- {"ondelete": "restrict"}, {"ondelete": "restrict"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_onupdate_restrict
- def test_nochange_onupdate_restrict(self):
- """test the RESTRICT option which MySQL doesn't report on"""
-
- diffs = self._fk_opts_fixture(
- {"onupdate": "restrict"}, {"onupdate": "restrict"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_ondelete_noaction
- def test_nochange_ondelete_noaction(self):
- """test the NO ACTION option which generally comes back as None"""
-
- diffs = self._fk_opts_fixture(
- {"ondelete": "no action"}, {"ondelete": "no action"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_onupdate
- def test_nochange_onupdate_noaction(self):
- """test the NO ACTION option which generally comes back as None"""
-
- diffs = self._fk_opts_fixture(
- {"onupdate": "no action"}, {"onupdate": "no action"}
- )
- eq_(diffs, [])
-
- @config.requirements.fk_ondelete_restrict
- def test_change_ondelete_from_restrict(self):
- """test the RESTRICT option which MySQL doesn't report on"""
-
- # note that this is impossible to detect if we change
- # from RESTRICT to NO ACTION on MySQL.
- diffs = self._fk_opts_fixture(
- {"ondelete": "restrict"}, {"ondelete": "cascade"}
- )
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate=None,
- ondelete=mock.ANY, # MySQL reports None, PG reports RESTRICT
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate=None,
- ondelete="cascade",
- )
-
- @config.requirements.fk_ondelete_restrict
- def test_change_onupdate_from_restrict(self):
- """test the RESTRICT option which MySQL doesn't report on"""
-
- # note that this is impossible to detect if we change
- # from RESTRICT to NO ACTION on MySQL.
- diffs = self._fk_opts_fixture(
- {"onupdate": "restrict"}, {"onupdate": "cascade"}
- )
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate=mock.ANY, # MySQL reports None, PG reports RESTRICT
- ondelete=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate="cascade",
- ondelete=None,
- )
-
- @config.requirements.fk_ondelete_is_reflected
- @config.requirements.fk_onupdate_is_reflected
- def test_ondelete_onupdate_combo(self):
- diffs = self._fk_opts_fixture(
- {"onupdate": "CASCADE", "ondelete": "SET NULL"},
- {"onupdate": "RESTRICT", "ondelete": "RESTRICT"},
- )
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate="CASCADE",
- ondelete="SET NULL",
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- onupdate="RESTRICT",
- ondelete="RESTRICT",
- )
-
- @config.requirements.fk_initially
- def test_add_initially_deferred(self):
- diffs = self._fk_opts_fixture({}, {"initially": "deferred"})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially="deferred",
- )
-
- @config.requirements.fk_initially
- def test_remove_initially_deferred(self):
- diffs = self._fk_opts_fixture({"initially": "deferred"}, {})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially="DEFERRED",
- deferrable=True,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially=None,
- )
-
- @config.requirements.fk_deferrable
- @config.requirements.fk_initially
- def test_add_initially_immediate_plus_deferrable(self):
- diffs = self._fk_opts_fixture(
- {}, {"initially": "immediate", "deferrable": True}
- )
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially="immediate",
- deferrable=True,
- )
-
- @config.requirements.fk_deferrable
- @config.requirements.fk_initially
- def test_remove_initially_immediate_plus_deferrable(self):
- diffs = self._fk_opts_fixture(
- {"initially": "immediate", "deferrable": True}, {}
- )
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially=None, # immediate is the default
- deferrable=True,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- initially=None,
- deferrable=None,
- )
-
- @config.requirements.fk_initially
- @config.requirements.fk_deferrable
- def test_add_initially_deferrable_nochange_one(self):
- diffs = self._fk_opts_fixture(
- {"deferrable": True, "initially": "immediate"},
- {"deferrable": True, "initially": "immediate"},
- )
-
- eq_(diffs, [])
-
- @config.requirements.fk_initially
- @config.requirements.fk_deferrable
- def test_add_initially_deferrable_nochange_two(self):
- diffs = self._fk_opts_fixture(
- {"deferrable": True, "initially": "deferred"},
- {"deferrable": True, "initially": "deferred"},
- )
-
- eq_(diffs, [])
-
- @config.requirements.fk_initially
- @config.requirements.fk_deferrable
- def test_add_initially_deferrable_nochange_three(self):
- diffs = self._fk_opts_fixture(
- {"deferrable": None, "initially": "deferred"},
- {"deferrable": None, "initially": "deferred"},
- )
-
- eq_(diffs, [])
-
- @config.requirements.fk_deferrable
- def test_add_deferrable(self):
- diffs = self._fk_opts_fixture({}, {"deferrable": True})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- deferrable=None,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- deferrable=True,
- )
-
- @config.requirements.fk_deferrable_is_reflected
- def test_remove_deferrable(self):
- diffs = self._fk_opts_fixture({"deferrable": True}, {})
-
- self._assert_fk_diff(
- diffs[0],
- "remove_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- deferrable=True,
- conditional_name="servergenerated",
- )
-
- self._assert_fk_diff(
- diffs[1],
- "add_fk",
- "user",
- ["tid"],
- "some_table",
- ["id"],
- deferrable=None,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py
deleted file mode 100644
index 3dee9fc..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py
+++ /dev/null
@@ -1,226 +0,0 @@
-import sqlalchemy as sa
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import MetaData
-from sqlalchemy import Table
-
-from alembic.util import sqla_compat
-from ._autogen_fixtures import AutogenFixtureTest
-from ... import testing
-from ...testing import config
-from ...testing import eq_
-from ...testing import is_true
-from ...testing import TestBase
-
-
-class AutogenerateIdentityTest(AutogenFixtureTest, TestBase):
- __requires__ = ("identity_columns",)
- __backend__ = True
-
- def test_add_identity_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table("user", m1, Column("other", sa.Text))
-
- Table(
- "user",
- m2,
- Column("other", sa.Text),
- Column(
- "id",
- Integer,
- sa.Identity(start=5, increment=7),
- primary_key=True,
- ),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "add_column")
- eq_(diffs[0][2], "user")
- eq_(diffs[0][3].name, "id")
- i = diffs[0][3].identity
-
- is_true(isinstance(i, sa.Identity))
- eq_(i.start, 5)
- eq_(i.increment, 7)
-
- def test_remove_identity_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "user",
- m1,
- Column(
- "id",
- Integer,
- sa.Identity(start=2, increment=3),
- primary_key=True,
- ),
- )
-
- Table("user", m2)
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs[0][0], "remove_column")
- eq_(diffs[0][2], "user")
- c = diffs[0][3]
- eq_(c.name, "id")
-
- is_true(isinstance(c.identity, sa.Identity))
- eq_(c.identity.start, 2)
- eq_(c.identity.increment, 3)
-
- def test_no_change_identity_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- for m in (m1, m2):
- id_ = sa.Identity(start=2)
- Table("user", m, Column("id", Integer, id_))
-
- diffs = self._fixture(m1, m2)
-
- eq_(diffs, [])
-
- def test_dialect_kwargs_changes(self):
- m1 = MetaData()
- m2 = MetaData()
-
- if sqla_compat.identity_has_dialect_kwargs:
- args = {"oracle_on_null": True, "oracle_order": True}
- else:
- args = {"on_null": True, "order": True}
-
- Table("user", m1, Column("id", Integer, sa.Identity(start=2)))
- id_ = sa.Identity(start=2, **args)
- Table("user", m2, Column("id", Integer, id_))
-
- diffs = self._fixture(m1, m2)
- if config.db.name == "oracle":
- is_true(len(diffs), 1)
- eq_(diffs[0][0][0], "modify_default")
- else:
- eq_(diffs, [])
-
- @testing.combinations(
- (None, dict(start=2)),
- (dict(start=2), None),
- (dict(start=2), dict(start=2, increment=7)),
- (dict(always=False), dict(always=True)),
- (
- dict(start=1, minvalue=0, maxvalue=100, cycle=True),
- dict(start=1, minvalue=0, maxvalue=100, cycle=False),
- ),
- (
- dict(start=10, increment=3, maxvalue=9999),
- dict(start=10, increment=1, maxvalue=3333),
- ),
- )
- @config.requirements.identity_columns_alter
- def test_change_identity(self, before, after):
- arg_before = (sa.Identity(**before),) if before else ()
- arg_after = (sa.Identity(**after),) if after else ()
-
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "user",
- m1,
- Column("id", Integer, *arg_before),
- Column("other", sa.Text),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, *arg_after),
- Column("other", sa.Text),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(len(diffs[0]), 1)
- diffs = diffs[0][0]
- eq_(diffs[0], "modify_default")
- eq_(diffs[2], "user")
- eq_(diffs[3], "id")
- old = diffs[5]
- new = diffs[6]
-
- def check(kw, idt):
- if kw:
- is_true(isinstance(idt, sa.Identity))
- for k, v in kw.items():
- eq_(getattr(idt, k), v)
- else:
- is_true(idt in (None, False))
-
- check(before, old)
- check(after, new)
-
- def test_add_identity_to_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "user",
- m1,
- Column("id", Integer),
- Column("other", sa.Text),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer, sa.Identity(start=2, maxvalue=1000)),
- Column("other", sa.Text),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(len(diffs[0]), 1)
- diffs = diffs[0][0]
- eq_(diffs[0], "modify_default")
- eq_(diffs[2], "user")
- eq_(diffs[3], "id")
- eq_(diffs[5], None)
- added = diffs[6]
-
- is_true(isinstance(added, sa.Identity))
- eq_(added.start, 2)
- eq_(added.maxvalue, 1000)
-
- def test_remove_identity_from_column(self):
- m1 = MetaData()
- m2 = MetaData()
-
- Table(
- "user",
- m1,
- Column("id", Integer, sa.Identity(start=2, maxvalue=1000)),
- Column("other", sa.Text),
- )
-
- Table(
- "user",
- m2,
- Column("id", Integer),
- Column("other", sa.Text),
- )
-
- diffs = self._fixture(m1, m2)
-
- eq_(len(diffs[0]), 1)
- diffs = diffs[0][0]
- eq_(diffs[0], "modify_default")
- eq_(diffs[2], "user")
- eq_(diffs[3], "id")
- eq_(diffs[6], None)
- removed = diffs[5]
-
- is_true(isinstance(removed, sa.Identity))
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py
deleted file mode 100644
index df2d9af..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py
+++ /dev/null
@@ -1,364 +0,0 @@
-import io
-
-from ...migration import MigrationContext
-from ...testing import assert_raises
-from ...testing import config
-from ...testing import eq_
-from ...testing import is_
-from ...testing import is_false
-from ...testing import is_not_
-from ...testing import is_true
-from ...testing import ne_
-from ...testing.fixtures import TestBase
-
-
-class MigrationTransactionTest(TestBase):
- __backend__ = True
-
- conn = None
-
- def _fixture(self, opts):
- self.conn = conn = config.db.connect()
-
- if opts.get("as_sql", False):
- self.context = MigrationContext.configure(
- dialect=conn.dialect, opts=opts
- )
- self.context.output_buffer = self.context.impl.output_buffer = (
- io.StringIO()
- )
- else:
- self.context = MigrationContext.configure(
- connection=conn, opts=opts
- )
- return self.context
-
- def teardown_method(self):
- if self.conn:
- self.conn.close()
-
- def test_proxy_transaction_rollback(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
-
- is_false(self.conn.in_transaction())
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
- proxy.rollback()
- is_false(self.conn.in_transaction())
-
- def test_proxy_transaction_commit(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
- proxy.commit()
- is_false(self.conn.in_transaction())
-
- def test_proxy_transaction_contextmanager_commit(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
- with proxy:
- pass
- is_false(self.conn.in_transaction())
-
- def test_proxy_transaction_contextmanager_rollback(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
-
- def go():
- with proxy:
- raise Exception("hi")
-
- assert_raises(Exception, go)
- is_false(self.conn.in_transaction())
-
- def test_proxy_transaction_contextmanager_explicit_rollback(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
-
- with proxy:
- is_true(self.conn.in_transaction())
- proxy.rollback()
- is_false(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
-
- def test_proxy_transaction_contextmanager_explicit_commit(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
- proxy = context.begin_transaction(_per_migration=True)
- is_true(self.conn.in_transaction())
-
- with proxy:
- is_true(self.conn.in_transaction())
- proxy.commit()
- is_false(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
-
- def test_transaction_per_migration_transactional_ddl(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": True}
- )
-
- is_false(self.conn.in_transaction())
-
- with context.begin_transaction():
- is_false(self.conn.in_transaction())
- with context.begin_transaction(_per_migration=True):
- is_true(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
- is_false(self.conn.in_transaction())
-
- def test_transaction_per_migration_non_transactional_ddl(self):
- context = self._fixture(
- {"transaction_per_migration": True, "transactional_ddl": False}
- )
-
- is_false(self.conn.in_transaction())
-
- with context.begin_transaction():
- is_false(self.conn.in_transaction())
- with context.begin_transaction(_per_migration=True):
- is_true(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
- is_false(self.conn.in_transaction())
-
- def test_transaction_per_all_transactional_ddl(self):
- context = self._fixture({"transactional_ddl": True})
-
- is_false(self.conn.in_transaction())
-
- with context.begin_transaction():
- is_true(self.conn.in_transaction())
- with context.begin_transaction(_per_migration=True):
- is_true(self.conn.in_transaction())
-
- is_true(self.conn.in_transaction())
- is_false(self.conn.in_transaction())
-
- def test_transaction_per_all_non_transactional_ddl(self):
- context = self._fixture({"transactional_ddl": False})
-
- is_false(self.conn.in_transaction())
-
- with context.begin_transaction():
- is_false(self.conn.in_transaction())
- with context.begin_transaction(_per_migration=True):
- is_true(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
- is_false(self.conn.in_transaction())
-
- def test_transaction_per_all_sqlmode(self):
- context = self._fixture({"as_sql": True})
-
- context.execute("step 1")
- with context.begin_transaction():
- context.execute("step 2")
- with context.begin_transaction(_per_migration=True):
- context.execute("step 3")
-
- context.execute("step 4")
- context.execute("step 5")
-
- if context.impl.transactional_ddl:
- self._assert_impl_steps(
- "step 1",
- "BEGIN",
- "step 2",
- "step 3",
- "step 4",
- "COMMIT",
- "step 5",
- )
- else:
- self._assert_impl_steps(
- "step 1", "step 2", "step 3", "step 4", "step 5"
- )
-
- def test_transaction_per_migration_sqlmode(self):
- context = self._fixture(
- {"as_sql": True, "transaction_per_migration": True}
- )
-
- context.execute("step 1")
- with context.begin_transaction():
- context.execute("step 2")
- with context.begin_transaction(_per_migration=True):
- context.execute("step 3")
-
- context.execute("step 4")
- context.execute("step 5")
-
- if context.impl.transactional_ddl:
- self._assert_impl_steps(
- "step 1",
- "step 2",
- "BEGIN",
- "step 3",
- "COMMIT",
- "step 4",
- "step 5",
- )
- else:
- self._assert_impl_steps(
- "step 1", "step 2", "step 3", "step 4", "step 5"
- )
-
- @config.requirements.autocommit_isolation
- def test_autocommit_block(self):
- context = self._fixture({"transaction_per_migration": True})
-
- is_false(self.conn.in_transaction())
-
- with context.begin_transaction():
- is_false(self.conn.in_transaction())
- with context.begin_transaction(_per_migration=True):
- is_true(self.conn.in_transaction())
-
- with context.autocommit_block():
- # in 1.x, self.conn is separate due to the
- # execution_options call. however for future they are the
- # same connection and there is a "transaction" block
- # despite autocommit
- if self.is_sqlalchemy_future:
- is_(context.connection, self.conn)
- else:
- is_not_(context.connection, self.conn)
- is_false(self.conn.in_transaction())
-
- eq_(
- context.connection._execution_options[
- "isolation_level"
- ],
- "AUTOCOMMIT",
- )
-
- ne_(
- context.connection._execution_options.get(
- "isolation_level", None
- ),
- "AUTOCOMMIT",
- )
- is_true(self.conn.in_transaction())
-
- is_false(self.conn.in_transaction())
- is_false(self.conn.in_transaction())
-
- @config.requirements.autocommit_isolation
- def test_autocommit_block_no_transaction(self):
- context = self._fixture({"transaction_per_migration": True})
-
- is_false(self.conn.in_transaction())
-
- with context.autocommit_block():
- is_true(context.connection.in_transaction())
-
- # in 1.x, self.conn is separate due to the execution_options
- # call. however for future they are the same connection and there
- # is a "transaction" block despite autocommit
- if self.is_sqlalchemy_future:
- is_(context.connection, self.conn)
- else:
- is_not_(context.connection, self.conn)
- is_false(self.conn.in_transaction())
-
- eq_(
- context.connection._execution_options["isolation_level"],
- "AUTOCOMMIT",
- )
-
- ne_(
- context.connection._execution_options.get("isolation_level", None),
- "AUTOCOMMIT",
- )
-
- is_false(self.conn.in_transaction())
-
- def test_autocommit_block_transactional_ddl_sqlmode(self):
- context = self._fixture(
- {
- "transaction_per_migration": True,
- "transactional_ddl": True,
- "as_sql": True,
- }
- )
-
- with context.begin_transaction():
- context.execute("step 1")
- with context.begin_transaction(_per_migration=True):
- context.execute("step 2")
-
- with context.autocommit_block():
- context.execute("step 3")
-
- context.execute("step 4")
-
- context.execute("step 5")
-
- self._assert_impl_steps(
- "step 1",
- "BEGIN",
- "step 2",
- "COMMIT",
- "step 3",
- "BEGIN",
- "step 4",
- "COMMIT",
- "step 5",
- )
-
- def test_autocommit_block_nontransactional_ddl_sqlmode(self):
- context = self._fixture(
- {
- "transaction_per_migration": True,
- "transactional_ddl": False,
- "as_sql": True,
- }
- )
-
- with context.begin_transaction():
- context.execute("step 1")
- with context.begin_transaction(_per_migration=True):
- context.execute("step 2")
-
- with context.autocommit_block():
- context.execute("step 3")
-
- context.execute("step 4")
-
- context.execute("step 5")
-
- self._assert_impl_steps(
- "step 1", "step 2", "step 3", "step 4", "step 5"
- )
-
- def _assert_impl_steps(self, *steps):
- to_check = self.context.output_buffer.getvalue()
-
- self.context.impl.output_buffer = buf = io.StringIO()
- for step in steps:
- if step == "BEGIN":
- self.context.impl.emit_begin()
- elif step == "COMMIT":
- self.context.impl.emit_commit()
- else:
- self.context.impl._exec(step)
-
- eq_(to_check, buf.getvalue())
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py
deleted file mode 100644
index a63b3f2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""Test against the builders in the op.* module."""
-
-from sqlalchemy import Column
-from sqlalchemy import event
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy.sql import text
-
-from ...testing.fixtures import AlterColRoundTripFixture
-from ...testing.fixtures import TestBase
-
-
-@event.listens_for(Table, "after_parent_attach")
-def _add_cols(table, metadata):
- if table.name == "tbl_with_auto_appended_column":
- table.append_column(Column("bat", Integer))
-
-
-class BackendAlterColumnTest(AlterColRoundTripFixture, TestBase):
- __backend__ = True
-
- def test_rename_column(self):
- self._run_alter_col({}, {"name": "newname"})
-
- def test_modify_type_int_str(self):
- self._run_alter_col({"type": Integer()}, {"type": String(50)})
-
- def test_add_server_default_int(self):
- self._run_alter_col({"type": Integer}, {"server_default": text("5")})
-
- def test_modify_server_default_int(self):
- self._run_alter_col(
- {"type": Integer, "server_default": text("2")},
- {"server_default": text("5")},
- )
-
- def test_modify_nullable_to_non(self):
- self._run_alter_col({}, {"nullable": False})
-
- def test_modify_non_nullable_to_nullable(self):
- self._run_alter_col({"nullable": False}, {"nullable": True})
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/util.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/util.py
deleted file mode 100644
index 4517a69..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/util.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# testing/util.py
-# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors
-#
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-from __future__ import annotations
-
-import types
-from typing import Union
-
-from sqlalchemy.util import inspect_getfullargspec
-
-from ..util import sqla_2
-
-
-def flag_combinations(*combinations):
- """A facade around @testing.combinations() oriented towards boolean
- keyword-based arguments.
-
- Basically generates a nice looking identifier based on the keywords
- and also sets up the argument names.
-
- E.g.::
-
- @testing.flag_combinations(
- dict(lazy=False, passive=False),
- dict(lazy=True, passive=False),
- dict(lazy=False, passive=True),
- dict(lazy=False, passive=True, raiseload=True),
- )
-
-
- would result in::
-
- @testing.combinations(
- ('', False, False, False),
- ('lazy', True, False, False),
- ('lazy_passive', True, True, False),
- ('lazy_passive', True, True, True),
- id_='iaaa',
- argnames='lazy,passive,raiseload'
- )
-
- """
- from sqlalchemy.testing import config
-
- keys = set()
-
- for d in combinations:
- keys.update(d)
-
- keys = sorted(keys)
-
- return config.combinations(
- *[
- ("_".join(k for k in keys if d.get(k, False)),)
- + tuple(d.get(k, False) for k in keys)
- for d in combinations
- ],
- id_="i" + ("a" * len(keys)),
- argnames=",".join(keys),
- )
-
-
-def resolve_lambda(__fn, **kw):
- """Given a no-arg lambda and a namespace, return a new lambda that
- has all the values filled in.
-
- This is used so that we can have module-level fixtures that
- refer to instance-level variables using lambdas.
-
- """
-
- pos_args = inspect_getfullargspec(__fn)[0]
- pass_pos_args = {arg: kw.pop(arg) for arg in pos_args}
- glb = dict(__fn.__globals__)
- glb.update(kw)
- new_fn = types.FunctionType(__fn.__code__, glb)
- return new_fn(**pass_pos_args)
-
-
-def metadata_fixture(ddl="function"):
- """Provide MetaData for a pytest fixture."""
-
- from sqlalchemy.testing import config
- from . import fixture_functions
-
- def decorate(fn):
- def run_ddl(self):
- from sqlalchemy import schema
-
- metadata = self.metadata = schema.MetaData()
- try:
- result = fn(self, metadata)
- metadata.create_all(config.db)
- # TODO:
- # somehow get a per-function dml erase fixture here
- yield result
- finally:
- metadata.drop_all(config.db)
-
- return fixture_functions.fixture(scope=ddl)(run_ddl)
-
- return decorate
-
-
-def _safe_int(value: str) -> Union[int, str]:
- try:
- return int(value)
- except:
- return value
-
-
-def testing_engine(url=None, options=None, future=False):
- from sqlalchemy.testing import config
- from sqlalchemy.testing.engines import testing_engine
-
- if not future:
- future = getattr(config._current.options, "future_engine", False)
-
- if not sqla_2:
- kw = {"future": future} if future else {}
- else:
- kw = {}
- return testing_engine(url, options, **kw)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/testing/warnings.py b/backend/.venv/lib/python3.12/site-packages/alembic/testing/warnings.py
deleted file mode 100644
index 86d45a0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/testing/warnings.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# testing/warnings.py
-# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
-#
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-
-import warnings
-
-from sqlalchemy import exc as sa_exc
-
-
-def setup_filters():
- """Set global warning behavior for the test suite."""
-
- warnings.resetwarnings()
-
- warnings.filterwarnings("error", category=sa_exc.SADeprecationWarning)
- warnings.filterwarnings("error", category=sa_exc.SAWarning)
-
- # some selected deprecations...
- warnings.filterwarnings("error", category=DeprecationWarning)
- try:
- import pytest
- except ImportError:
- pass
- else:
- warnings.filterwarnings(
- "once", category=pytest.PytestDeprecationWarning
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__init__.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/__init__.py
deleted file mode 100644
index 8f3f685..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from .editor import open_in_editor as open_in_editor
-from .exc import AutogenerateDiffsDetected as AutogenerateDiffsDetected
-from .exc import CommandError as CommandError
-from .exc import DatabaseNotAtHead as DatabaseNotAtHead
-from .langhelpers import _with_legacy_names as _with_legacy_names
-from .langhelpers import asbool as asbool
-from .langhelpers import dedupe_tuple as dedupe_tuple
-from .langhelpers import Dispatcher as Dispatcher
-from .langhelpers import DispatchPriority as DispatchPriority
-from .langhelpers import EMPTY_DICT as EMPTY_DICT
-from .langhelpers import immutabledict as immutabledict
-from .langhelpers import memoized_property as memoized_property
-from .langhelpers import ModuleClsProxy as ModuleClsProxy
-from .langhelpers import not_none as not_none
-from .langhelpers import PriorityDispatcher as PriorityDispatcher
-from .langhelpers import PriorityDispatchResult as PriorityDispatchResult
-from .langhelpers import rev_id as rev_id
-from .langhelpers import to_list as to_list
-from .langhelpers import to_tuple as to_tuple
-from .langhelpers import unique_list as unique_list
-from .messaging import err as err
-from .messaging import format_as_comma as format_as_comma
-from .messaging import msg as msg
-from .messaging import obfuscate_url_pw as obfuscate_url_pw
-from .messaging import status as status
-from .messaging import warn as warn
-from .messaging import warn_deprecated as warn_deprecated
-from .messaging import write_outstream as write_outstream
-from .pyfiles import coerce_resource_to_filename as coerce_resource_to_filename
-from .pyfiles import load_python_file as load_python_file
-from .pyfiles import pyc_file_from_path as pyc_file_from_path
-from .pyfiles import template_to_file as template_to_file
-from .sqla_compat import sqla_2 as sqla_2
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 64d7693..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc
deleted file mode 100644
index d60ba0e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc
deleted file mode 100644
index 741491a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc
deleted file mode 100644
index 2fef5f8..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc
deleted file mode 100644
index e9b8ade..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc
deleted file mode 100644
index 8de8efd..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc
deleted file mode 100644
index a21e4ad..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc
deleted file mode 100644
index 3cc1244..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/compat.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/compat.py
deleted file mode 100644
index 527ffdc..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/compat.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# mypy: no-warn-unused-ignores
-
-from __future__ import annotations
-
-from configparser import ConfigParser
-from importlib import metadata
-from importlib.metadata import EntryPoint
-import io
-import os
-from pathlib import Path
-import sys
-import typing
-from typing import Any
-from typing import Iterator
-from typing import Sequence
-
-if True:
- # zimports hack for too-long names
- from sqlalchemy.util import ( # noqa: F401
- inspect_getfullargspec as inspect_getfullargspec,
- )
- from sqlalchemy.util.compat import ( # noqa: F401
- inspect_formatargspec as inspect_formatargspec,
- )
-
-is_posix = os.name == "posix"
-
-py314 = sys.version_info >= (3, 14)
-py313 = sys.version_info >= (3, 13)
-py312 = sys.version_info >= (3, 12)
-py311 = sys.version_info >= (3, 11)
-
-
-# produce a wrapper that allows encoded text to stream
-# into a given buffer, but doesn't close it.
-# not sure of a more idiomatic approach to this.
-class EncodedIO(io.TextIOWrapper):
- def close(self) -> None:
- pass
-
-
-if py311:
- import tomllib as tomllib
-else:
- import tomli as tomllib # type: ignore # noqa
-
-
-if py312:
-
- def path_walk(
- path: Path, *, top_down: bool = True
- ) -> Iterator[tuple[Path, list[str], list[str]]]:
- return Path.walk(path)
-
- def path_relative_to(
- path: Path, other: Path, *, walk_up: bool = False
- ) -> Path:
- return path.relative_to(other, walk_up=walk_up)
-
-else:
-
- def path_walk(
- path: Path, *, top_down: bool = True
- ) -> Iterator[tuple[Path, list[str], list[str]]]:
- for root, dirs, files in os.walk(path, topdown=top_down):
- yield Path(root), dirs, files
-
- def path_relative_to(
- path: Path, other: Path, *, walk_up: bool = False
- ) -> Path:
- """
- Calculate the relative path of 'path' with respect to 'other',
- optionally allowing 'path' to be outside the subtree of 'other'.
-
- OK I used AI for this, sorry
-
- """
- try:
- return path.relative_to(other)
- except ValueError:
- if walk_up:
- other_ancestors = list(other.parents) + [other]
- for ancestor in other_ancestors:
- try:
- return path.relative_to(ancestor)
- except ValueError:
- continue
- raise ValueError(
- f"{path} is not in the same subtree as {other}"
- )
- else:
- raise
-
-
-def importlib_metadata_get(group: str) -> Sequence[EntryPoint]:
- """provide a facade for metadata.entry_points().
-
- This is no longer a "compat" function as of Python 3.10, however
- the function is widely referenced in the test suite and elsewhere so is
- still in this module for compatibility reasons.
-
- """
- return metadata.entry_points().select(group=group)
-
-
-def formatannotation_fwdref(
- annotation: Any, base_module: Any | None = None
-) -> str:
- """vendored from python 3.7"""
- # copied over _formatannotation from sqlalchemy 2.0
-
- if isinstance(annotation, str):
- return annotation
-
- if getattr(annotation, "__module__", None) == "typing":
- return repr(annotation).replace("typing.", "").replace("~", "")
- if isinstance(annotation, type):
- if annotation.__module__ in ("builtins", base_module):
- return repr(annotation.__qualname__)
- return annotation.__module__ + "." + annotation.__qualname__
- elif isinstance(annotation, typing.TypeVar):
- return repr(annotation).replace("~", "")
- return repr(annotation).replace("~", "")
-
-
-def read_config_parser(
- file_config: ConfigParser,
- file_argument: list[str | os.PathLike[str]],
-) -> list[str]:
- return file_config.read(file_argument, encoding="locale")
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/editor.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/editor.py
deleted file mode 100644
index f1d1557..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/editor.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from __future__ import annotations
-
-import os
-from os.path import exists
-from os.path import join
-from os.path import splitext
-from subprocess import check_call
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import Optional
-
-from .compat import is_posix
-from .exc import CommandError
-
-
-def open_in_editor(
- filename: str, environ: Optional[Dict[str, str]] = None
-) -> None:
- """
- Opens the given file in a text editor. If the environment variable
- ``EDITOR`` is set, this is taken as preference.
-
- Otherwise, a list of commonly installed editors is tried.
-
- If no editor matches, an :py:exc:`OSError` is raised.
-
- :param filename: The filename to open. Will be passed verbatim to the
- editor command.
- :param environ: An optional drop-in replacement for ``os.environ``. Used
- mainly for testing.
- """
- env = os.environ if environ is None else environ
- try:
- editor = _find_editor(env)
- check_call([editor, filename])
- except Exception as exc:
- raise CommandError("Error executing editor (%s)" % (exc,)) from exc
-
-
-def _find_editor(environ: Mapping[str, str]) -> str:
- candidates = _default_editors()
- for i, var in enumerate(("EDITOR", "VISUAL")):
- if var in environ:
- user_choice = environ[var]
- if exists(user_choice):
- return user_choice
- if os.sep not in user_choice:
- candidates.insert(i, user_choice)
-
- for candidate in candidates:
- path = _find_executable(candidate, environ)
- if path is not None:
- return path
- raise OSError(
- "No suitable editor found. Please set the "
- '"EDITOR" or "VISUAL" environment variables'
- )
-
-
-def _find_executable(
- candidate: str, environ: Mapping[str, str]
-) -> Optional[str]:
- # Assuming this is on the PATH, we need to determine it's absolute
- # location. Otherwise, ``check_call`` will fail
- if not is_posix and splitext(candidate)[1] != ".exe":
- candidate += ".exe"
- for path in environ.get("PATH", "").split(os.pathsep):
- value = join(path, candidate)
- if exists(value):
- return value
- return None
-
-
-def _default_editors() -> List[str]:
- # Look for an editor. Prefer the user's choice by env-var, fall back to
- # most commonly installed editor (nano/vim)
- if is_posix:
- return ["sensible-editor", "editor", "nano", "vim", "code"]
- else:
- return ["code.exe", "notepad++.exe", "notepad.exe"]
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/exc.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/exc.py
deleted file mode 100644
index 4658f78..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/exc.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import annotations
-
-from typing import Any
-from typing import List
-from typing import Tuple
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
- from alembic.autogenerate import RevisionContext
-
-
-class CommandError(Exception):
- """Base command error for all exceptions"""
-
-
-class DatabaseNotAtHead(CommandError):
- """Indicates the database is not at current head revisions.
-
- Raised by the :func:`.command.current` command when the
- :paramref:`.command.current.check_heads` parameter is used.
-
- .. versionadded:: 1.17.1
-
- """
-
-
-class AutogenerateDiffsDetected(CommandError):
- """Raised when diffs were detected by the :func:`.command.check`
- command.
-
- .. versionadded:: 1.9.0
-
- """
-
- def __init__(
- self,
- message: str,
- revision_context: RevisionContext,
- diffs: List[Tuple[Any, ...]],
- ) -> None:
- super().__init__(message)
- self.revision_context = revision_context
- self.diffs = diffs
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/langhelpers.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/langhelpers.py
deleted file mode 100644
index cf0df23..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/langhelpers.py
+++ /dev/null
@@ -1,445 +0,0 @@
-from __future__ import annotations
-
-import collections
-from collections.abc import Iterable
-import enum
-import textwrap
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import MutableMapping
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TypeVar
-import uuid
-import warnings
-
-from sqlalchemy.util import asbool as asbool # noqa: F401
-from sqlalchemy.util import immutabledict as immutabledict # noqa: F401
-from sqlalchemy.util import to_list as to_list # noqa: F401
-from sqlalchemy.util import unique_list as unique_list
-
-from .compat import inspect_getfullargspec
-
-if True:
- # zimports workaround :(
- from sqlalchemy.util import ( # noqa: F401
- memoized_property as memoized_property,
- )
-
-
-EMPTY_DICT: Mapping[Any, Any] = immutabledict()
-_T = TypeVar("_T", bound=Any)
-
-_C = TypeVar("_C", bound=Callable[..., Any])
-
-
-class _ModuleClsMeta(type):
- def __setattr__(cls, key: str, value: Callable[..., Any]) -> None:
- super().__setattr__(key, value)
- cls._update_module_proxies(key) # type: ignore
-
-
-class ModuleClsProxy(metaclass=_ModuleClsMeta):
- """Create module level proxy functions for the
- methods on a given class.
-
- The functions will have a compatible signature
- as the methods.
-
- """
-
- _setups: Dict[
- Type[Any],
- Tuple[
- Set[str],
- List[Tuple[MutableMapping[str, Any], MutableMapping[str, Any]]],
- ],
- ] = collections.defaultdict(lambda: (set(), []))
-
- @classmethod
- def _update_module_proxies(cls, name: str) -> None:
- attr_names, modules = cls._setups[cls]
- for globals_, locals_ in modules:
- cls._add_proxied_attribute(name, globals_, locals_, attr_names)
-
- def _install_proxy(self) -> None:
- attr_names, modules = self._setups[self.__class__]
- for globals_, locals_ in modules:
- globals_["_proxy"] = self
- for attr_name in attr_names:
- globals_[attr_name] = getattr(self, attr_name)
-
- def _remove_proxy(self) -> None:
- attr_names, modules = self._setups[self.__class__]
- for globals_, locals_ in modules:
- globals_["_proxy"] = None
- for attr_name in attr_names:
- del globals_[attr_name]
-
- @classmethod
- def create_module_class_proxy(
- cls,
- globals_: MutableMapping[str, Any],
- locals_: MutableMapping[str, Any],
- ) -> None:
- attr_names, modules = cls._setups[cls]
- modules.append((globals_, locals_))
- cls._setup_proxy(globals_, locals_, attr_names)
-
- @classmethod
- def _setup_proxy(
- cls,
- globals_: MutableMapping[str, Any],
- locals_: MutableMapping[str, Any],
- attr_names: Set[str],
- ) -> None:
- for methname in dir(cls):
- cls._add_proxied_attribute(methname, globals_, locals_, attr_names)
-
- @classmethod
- def _add_proxied_attribute(
- cls,
- methname: str,
- globals_: MutableMapping[str, Any],
- locals_: MutableMapping[str, Any],
- attr_names: Set[str],
- ) -> None:
- if not methname.startswith("_"):
- meth = getattr(cls, methname)
- if callable(meth):
- locals_[methname] = cls._create_method_proxy(
- methname, globals_, locals_
- )
- else:
- attr_names.add(methname)
-
- @classmethod
- def _create_method_proxy(
- cls,
- name: str,
- globals_: MutableMapping[str, Any],
- locals_: MutableMapping[str, Any],
- ) -> Callable[..., Any]:
- fn = getattr(cls, name)
-
- def _name_error(name: str, from_: Exception) -> NoReturn:
- raise NameError(
- "Can't invoke function '%s', as the proxy object has "
- "not yet been "
- "established for the Alembic '%s' class. "
- "Try placing this code inside a callable."
- % (name, cls.__name__)
- ) from from_
-
- globals_["_name_error"] = _name_error
-
- translations = getattr(fn, "_legacy_translations", [])
- if translations:
- spec = inspect_getfullargspec(fn)
- if spec[0] and spec[0][0] == "self":
- spec[0].pop(0)
-
- outer_args = inner_args = "*args, **kw"
- translate_str = "args, kw = _translate(%r, %r, %r, args, kw)" % (
- fn.__name__,
- tuple(spec),
- translations,
- )
-
- def translate(
- fn_name: str, spec: Any, translations: Any, args: Any, kw: Any
- ) -> Any:
- return_kw = {}
- return_args = []
-
- for oldname, newname in translations:
- if oldname in kw:
- warnings.warn(
- "Argument %r is now named %r "
- "for method %s()." % (oldname, newname, fn_name)
- )
- return_kw[newname] = kw.pop(oldname)
- return_kw.update(kw)
-
- args = list(args)
- if spec[3]:
- pos_only = spec[0][: -len(spec[3])]
- else:
- pos_only = spec[0]
- for arg in pos_only:
- if arg not in return_kw:
- try:
- return_args.append(args.pop(0))
- except IndexError:
- raise TypeError(
- "missing required positional argument: %s"
- % arg
- )
- return_args.extend(args)
-
- return return_args, return_kw
-
- globals_["_translate"] = translate
- else:
- outer_args = "*args, **kw"
- inner_args = "*args, **kw"
- translate_str = ""
-
- func_text = textwrap.dedent(
- """\
- def %(name)s(%(args)s):
- %(doc)r
- %(translate)s
- try:
- p = _proxy
- except NameError as ne:
- _name_error('%(name)s', ne)
- return _proxy.%(name)s(%(apply_kw)s)
- e
- """
- % {
- "name": name,
- "translate": translate_str,
- "args": outer_args,
- "apply_kw": inner_args,
- "doc": fn.__doc__,
- }
- )
- lcl: MutableMapping[str, Any] = {}
-
- exec(func_text, cast("Dict[str, Any]", globals_), lcl)
- return cast("Callable[..., Any]", lcl[name])
-
-
-def _with_legacy_names(translations: Any) -> Any:
- def decorate(fn: _C) -> _C:
- fn._legacy_translations = translations # type: ignore[attr-defined]
- return fn
-
- return decorate
-
-
-def rev_id() -> str:
- return uuid.uuid4().hex[-12:]
-
-
-@overload
-def to_tuple(x: Any, default: Tuple[Any, ...]) -> Tuple[Any, ...]: ...
-
-
-@overload
-def to_tuple(x: None, default: Optional[_T] = ...) -> _T: ...
-
-
-@overload
-def to_tuple(
- x: Any, default: Optional[Tuple[Any, ...]] = None
-) -> Tuple[Any, ...]: ...
-
-
-def to_tuple(
- x: Any, default: Optional[Tuple[Any, ...]] = None
-) -> Optional[Tuple[Any, ...]]:
- if x is None:
- return default
- elif isinstance(x, str):
- return (x,)
- elif isinstance(x, Iterable):
- return tuple(x)
- else:
- return (x,)
-
-
-def dedupe_tuple(tup: Tuple[str, ...]) -> Tuple[str, ...]:
- return tuple(unique_list(tup))
-
-
-class PriorityDispatchResult(enum.Enum):
- """indicate an action after running a function within a
- :class:`.PriorityDispatcher`
-
- .. versionadded:: 1.18.0
-
- """
-
- CONTINUE = enum.auto()
- """Continue running more functions.
-
- Any return value that is not PriorityDispatchResult.STOP is equivalent
- to this.
-
- """
-
- STOP = enum.auto()
- """Stop running any additional functions within the subgroup"""
-
-
-class DispatchPriority(enum.IntEnum):
- """Indicate which of three sub-collections a function inside a
- :class:`.PriorityDispatcher` should be placed.
-
- .. versionadded:: 1.18.0
-
- """
-
- FIRST = 50
- """Run the funciton in the first batch of functions (highest priority)"""
-
- MEDIUM = 25
- """Run the function at normal priority (this is the default)"""
-
- LAST = 10
- """Run the function in the last batch of functions"""
-
-
-class Dispatcher:
- def __init__(self) -> None:
- self._registry: Dict[Tuple[Any, ...], Any] = {}
-
- def dispatch_for(
- self,
- target: Any,
- *,
- qualifier: str = "default",
- replace: bool = False,
- ) -> Callable[[_C], _C]:
- def decorate(fn: _C) -> _C:
- if (target, qualifier) in self._registry and not replace:
- raise ValueError(
- "Can not set dispatch function for object "
- f"{target!r}: key already exists. To replace "
- "existing function, use replace=True."
- )
- self._registry[(target, qualifier)] = fn
- return fn
-
- return decorate
-
- def dispatch(self, obj: Any, qualifier: str = "default") -> Any:
- if isinstance(obj, str):
- targets: Sequence[Any] = [obj]
- elif isinstance(obj, type):
- targets = obj.__mro__
- else:
- targets = type(obj).__mro__
-
- if qualifier != "default":
- qualifiers = [qualifier, "default"]
- else:
- qualifiers = ["default"]
-
- for spcls in targets:
- for qualifier in qualifiers:
- if (spcls, qualifier) in self._registry:
- return self._registry[(spcls, qualifier)]
- else:
- raise ValueError("no dispatch function for object: %s" % obj)
-
- def branch(self) -> Dispatcher:
- """Return a copy of this dispatcher that is independently
- writable."""
-
- d = Dispatcher()
- d._registry.update(self._registry)
- return d
-
-
-class PriorityDispatcher:
- """registers lists of functions at multiple levels of priorty and provides
- a target to invoke them in priority order.
-
- .. versionadded:: 1.18.0 - PriorityDispatcher replaces the job
- of Dispatcher(uselist=True)
-
- """
-
- def __init__(self) -> None:
- self._registry: dict[tuple[Any, ...], Any] = collections.defaultdict(
- list
- )
-
- def dispatch_for(
- self,
- target: str,
- *,
- priority: DispatchPriority = DispatchPriority.MEDIUM,
- qualifier: str = "default",
- subgroup: str | None = None,
- ) -> Callable[[_C], _C]:
- """return a decorator callable that registers a function at a
- given priority, with a given qualifier, to fire off for a given
- subgroup.
-
- It's important this remains as a decorator to support third party
- plugins who are populating the dispatcher using that style.
-
- """
-
- def decorate(fn: _C) -> _C:
- self._registry[(target, qualifier, priority)].append(
- (fn, subgroup)
- )
- return fn
-
- return decorate
-
- def dispatch(
- self, target: str, *, qualifier: str = "default"
- ) -> Callable[..., None]:
- """Provide a callable for the given target and qualifier."""
-
- if qualifier != "default":
- qualifiers = [qualifier, "default"]
- else:
- qualifiers = ["default"]
-
- def go(*arg: Any, **kw: Any) -> Any:
- results_by_subgroup: dict[str, PriorityDispatchResult] = {}
- for priority in DispatchPriority:
- for qualifier in qualifiers:
- for fn, subgroup in self._registry.get(
- (target, qualifier, priority), ()
- ):
- if (
- results_by_subgroup.get(
- subgroup, PriorityDispatchResult.CONTINUE
- )
- is PriorityDispatchResult.STOP
- ):
- continue
-
- result = fn(*arg, **kw)
- results_by_subgroup[subgroup] = result
-
- return go
-
- def branch(self) -> PriorityDispatcher:
- """Return a copy of this dispatcher that is independently
- writable."""
-
- d = PriorityDispatcher()
- d.populate_with(self)
- return d
-
- def populate_with(self, other: PriorityDispatcher) -> None:
- """Populate this PriorityDispatcher with the contents of another one.
-
- Additive, does not remove existing contents.
- """
- for k in other._registry:
- new_list = other._registry[k]
- self._registry[k].extend(new_list)
-
-
-def not_none(value: Optional[_T]) -> _T:
- assert value is not None
- return value
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/messaging.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/messaging.py
deleted file mode 100644
index 4c08f16..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/messaging.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import Iterable
-from contextlib import contextmanager
-import logging
-import sys
-import textwrap
-from typing import Iterator
-from typing import Optional
-from typing import TextIO
-from typing import Union
-import warnings
-
-from sqlalchemy.engine import url
-
-log = logging.getLogger(__name__)
-
-# disable "no handler found" errors
-logging.getLogger("alembic").addHandler(logging.NullHandler())
-
-
-try:
- import fcntl
- import termios
- import struct
-
- ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))
- _h, TERMWIDTH, _hp, _wp = struct.unpack("HHHH", ioctl)
- if TERMWIDTH <= 0: # can occur if running in emacs pseudo-tty
- TERMWIDTH = None
-except (ImportError, OSError):
- TERMWIDTH = None
-
-
-def write_outstream(
- stream: TextIO, *text: Union[str, bytes], quiet: bool = False
-) -> None:
- if quiet:
- return
- encoding = getattr(stream, "encoding", "ascii") or "ascii"
- for t in text:
- if not isinstance(t, bytes):
- t = t.encode(encoding, "replace")
- t = t.decode(encoding)
- try:
- stream.write(t)
- except OSError:
- # suppress "broken pipe" errors.
- # no known way to handle this on Python 3 however
- # as the exception is "ignored" (noisily) in TextIOWrapper.
- break
-
-
-@contextmanager
-def status(
- status_msg: str, newline: bool = False, quiet: bool = False
-) -> Iterator[None]:
- msg(status_msg + " ...", newline, flush=True, quiet=quiet)
- try:
- yield
- except:
- if not quiet:
- write_outstream(sys.stdout, " FAILED\n")
- raise
- else:
- if not quiet:
- write_outstream(sys.stdout, " done\n")
-
-
-def err(message: str, quiet: bool = False) -> None:
- log.error(message)
- msg(f"FAILED: {message}", quiet=quiet)
- sys.exit(-1)
-
-
-def obfuscate_url_pw(input_url: str) -> str:
- return url.make_url(input_url).render_as_string(hide_password=True)
-
-
-def warn(msg: str, stacklevel: int = 2) -> None:
- warnings.warn(msg, UserWarning, stacklevel=stacklevel)
-
-
-def warn_deprecated(msg: str, stacklevel: int = 2) -> None:
- warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel)
-
-
-def msg(
- msg: str, newline: bool = True, flush: bool = False, quiet: bool = False
-) -> None:
- if quiet:
- return
- if TERMWIDTH is None:
- write_outstream(sys.stdout, msg)
- if newline:
- write_outstream(sys.stdout, "\n")
- else:
- # left indent output lines
- indent = " "
- lines = textwrap.wrap(
- msg,
- TERMWIDTH,
- initial_indent=indent,
- subsequent_indent=indent,
- )
- if len(lines) > 1:
- for line in lines[0:-1]:
- write_outstream(sys.stdout, line, "\n")
- write_outstream(sys.stdout, lines[-1], ("\n" if newline else ""))
- if flush:
- sys.stdout.flush()
-
-
-def format_as_comma(value: Optional[Union[str, Iterable[str]]]) -> str:
- if value is None:
- return ""
- elif isinstance(value, str):
- return value
- elif isinstance(value, Iterable):
- return ", ".join(value)
- else:
- raise ValueError("Don't know how to comma-format %r" % value)
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/pyfiles.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/pyfiles.py
deleted file mode 100644
index 135a42d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/pyfiles.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from __future__ import annotations
-
-import atexit
-from contextlib import ExitStack
-import importlib
-from importlib import resources
-import importlib.machinery
-import importlib.util
-import os
-import pathlib
-import re
-import tempfile
-from types import ModuleType
-from typing import Any
-from typing import Optional
-from typing import Union
-
-from mako import exceptions
-from mako.template import Template
-
-from .exc import CommandError
-
-
-def template_to_file(
- template_file: Union[str, os.PathLike[str]],
- dest: Union[str, os.PathLike[str]],
- output_encoding: str,
- *,
- append_with_newlines: bool = False,
- **kw: Any,
-) -> None:
- template = Template(filename=_preserving_path_as_str(template_file))
- try:
- output = template.render_unicode(**kw).encode(output_encoding)
- except:
- with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as ntf:
- ntf.write(
- exceptions.text_error_template()
- .render_unicode()
- .encode(output_encoding)
- )
- fname = ntf.name
- raise CommandError(
- "Template rendering failed; see %s for a "
- "template-oriented traceback." % fname
- )
- else:
- with open(dest, "ab" if append_with_newlines else "wb") as f:
- if append_with_newlines:
- f.write("\n\n".encode(output_encoding))
- f.write(output)
-
-
-def coerce_resource_to_filename(fname_or_resource: str) -> pathlib.Path:
- """Interpret a filename as either a filesystem location or as a package
- resource.
-
- Names that are non absolute paths and contain a colon
- are interpreted as resources and coerced to a file location.
-
- """
- # TODO: there seem to be zero tests for the package resource codepath
- if not os.path.isabs(fname_or_resource) and ":" in fname_or_resource:
- tokens = fname_or_resource.split(":")
-
- # from https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-filename # noqa E501
-
- file_manager = ExitStack()
- atexit.register(file_manager.close)
-
- ref = resources.files(tokens[0])
- for tok in tokens[1:]:
- ref = ref / tok
- fname_or_resource = file_manager.enter_context( # type: ignore[assignment] # noqa: E501
- resources.as_file(ref)
- )
- return pathlib.Path(fname_or_resource)
-
-
-def pyc_file_from_path(
- path: Union[str, os.PathLike[str]],
-) -> Optional[pathlib.Path]:
- """Given a python source path, locate the .pyc."""
-
- pathpath = pathlib.Path(path)
- candidate = pathlib.Path(
- importlib.util.cache_from_source(pathpath.as_posix())
- )
- if candidate.exists():
- return candidate
-
- # even for pep3147, fall back to the old way of finding .pyc files,
- # to support sourceless operation
- ext = pathpath.suffix
- for ext in importlib.machinery.BYTECODE_SUFFIXES:
- if pathpath.with_suffix(ext).exists():
- return pathpath.with_suffix(ext)
- else:
- return None
-
-
-def load_python_file(
- dir_: Union[str, os.PathLike[str]], filename: Union[str, os.PathLike[str]]
-) -> ModuleType:
- """Load a file from the given path as a Python module."""
-
- dir_ = pathlib.Path(dir_)
- filename_as_path = pathlib.Path(filename)
- filename = filename_as_path.name
-
- module_id = re.sub(r"\W", "_", filename)
- path = dir_ / filename
- ext = path.suffix
- if ext == ".py":
- if path.exists():
- module = load_module_py(module_id, path)
- else:
- pyc_path = pyc_file_from_path(path)
- if pyc_path is None:
- raise ImportError("Can't find Python file %s" % path)
- else:
- module = load_module_py(module_id, pyc_path)
- elif ext in (".pyc", ".pyo"):
- module = load_module_py(module_id, path)
- else:
- assert False
- return module
-
-
-def load_module_py(
- module_id: str, path: Union[str, os.PathLike[str]]
-) -> ModuleType:
- spec = importlib.util.spec_from_file_location(module_id, path)
- assert spec
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module) # type: ignore
- return module
-
-
-def _preserving_path_as_str(path: Union[str, os.PathLike[str]]) -> str:
- """receive str/pathlike and return a string.
-
- Does not convert an incoming string path to a Path first, to help with
- unit tests that are doing string path round trips without OS-specific
- processing if not necessary.
-
- """
- if isinstance(path, str):
- return path
- elif isinstance(path, pathlib.PurePath):
- return str(path)
- else:
- return str(pathlib.Path(path))
diff --git a/backend/.venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py b/backend/.venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py
deleted file mode 100644
index ff2f2c9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py
+++ /dev/null
@@ -1,510 +0,0 @@
-# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
-# mypy: no-warn-return-any, allow-any-generics
-
-from __future__ import annotations
-
-import contextlib
-import re
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import Optional
-from typing import Protocol
-from typing import Set
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from sqlalchemy import __version__
-from sqlalchemy import schema
-from sqlalchemy import sql
-from sqlalchemy import types as sqltypes
-from sqlalchemy.schema import CheckConstraint
-from sqlalchemy.schema import Column
-from sqlalchemy.schema import ForeignKeyConstraint
-from sqlalchemy.sql import visitors
-from sqlalchemy.sql.base import DialectKWArgs
-from sqlalchemy.sql.elements import BindParameter
-from sqlalchemy.sql.elements import ColumnClause
-from sqlalchemy.sql.elements import TextClause
-from sqlalchemy.sql.elements import UnaryExpression
-from sqlalchemy.sql.naming import _NONE_NAME as _NONE_NAME # type: ignore[attr-defined] # noqa: E501
-from sqlalchemy.sql.visitors import traverse
-from typing_extensions import TypeGuard
-
-if TYPE_CHECKING:
- from sqlalchemy import ClauseElement
- from sqlalchemy import Identity
- from sqlalchemy import Index
- from sqlalchemy import Table
- from sqlalchemy.engine import Connection
- from sqlalchemy.engine import Dialect
- from sqlalchemy.engine import Transaction
- from sqlalchemy.sql.base import ColumnCollection
- from sqlalchemy.sql.compiler import SQLCompiler
- from sqlalchemy.sql.elements import ColumnElement
- from sqlalchemy.sql.schema import Constraint
- from sqlalchemy.sql.schema import SchemaItem
-
-_CE = TypeVar("_CE", bound=Union["ColumnElement[Any]", "SchemaItem"])
-
-
-class _CompilerProtocol(Protocol):
- def __call__(self, element: Any, compiler: Any, **kw: Any) -> str: ...
-
-
-def _safe_int(value: str) -> Union[int, str]:
- try:
- return int(value)
- except:
- return value
-
-
-_vers = tuple(
- [_safe_int(x) for x in re.findall(r"(\d+|[abc]\d)", __version__)]
-)
-# https://docs.sqlalchemy.org/en/latest/changelog/changelog_14.html#change-0c6e0cc67dfe6fac5164720e57ef307d
-sqla_14_18 = _vers >= (1, 4, 18)
-sqla_14_26 = _vers >= (1, 4, 26)
-sqla_2 = _vers >= (2,)
-sqla_2_0_25 = _vers >= (2, 25)
-sqla_2_1 = _vers >= (2, 1)
-sqlalchemy_version = __version__
-
-if TYPE_CHECKING:
-
- def compiles(
- element: Type[ClauseElement], *dialects: str
- ) -> Callable[[_CompilerProtocol], _CompilerProtocol]: ...
-
-else:
- from sqlalchemy.ext.compiler import compiles # noqa: I100,I202
-
-
-identity_has_dialect_kwargs = issubclass(schema.Identity, DialectKWArgs)
-
-
-def _get_identity_options_dict(
- identity: Union[Identity, schema.Sequence, None],
- dialect_kwargs: bool = False,
-) -> Dict[str, Any]:
- if identity is None:
- return {}
- elif identity_has_dialect_kwargs:
- assert hasattr(identity, "_as_dict")
- as_dict = identity._as_dict()
- if dialect_kwargs:
- assert isinstance(identity, DialectKWArgs)
- as_dict.update(identity.dialect_kwargs)
- else:
- as_dict = {}
- if isinstance(identity, schema.Identity):
- # always=None means something different than always=False
- as_dict["always"] = identity.always
- if identity.on_null is not None:
- as_dict["on_null"] = identity.on_null
- # attributes common to Identity and Sequence
- attrs = (
- "start",
- "increment",
- "minvalue",
- "maxvalue",
- "nominvalue",
- "nomaxvalue",
- "cycle",
- "cache",
- "order",
- )
- as_dict.update(
- {
- key: getattr(identity, key, None)
- for key in attrs
- if getattr(identity, key, None) is not None
- }
- )
- return as_dict
-
-
-if sqla_2:
- from sqlalchemy.sql.base import _NoneName
-else:
- from sqlalchemy.util import symbol as _NoneName # type: ignore[assignment]
-
-
-_ConstraintName = Union[None, str, _NoneName]
-_ConstraintNameDefined = Union[str, _NoneName]
-
-
-def constraint_name_defined(
- name: _ConstraintName,
-) -> TypeGuard[_ConstraintNameDefined]:
- return name is _NONE_NAME or isinstance(name, (str, _NoneName))
-
-
-def constraint_name_string(name: _ConstraintName) -> TypeGuard[str]:
- return isinstance(name, str)
-
-
-def constraint_name_or_none(name: _ConstraintName) -> Optional[str]:
- return name if constraint_name_string(name) else None
-
-
-AUTOINCREMENT_DEFAULT = "auto"
-
-
-@contextlib.contextmanager
-def _ensure_scope_for_ddl(
- connection: Optional[Connection],
-) -> Iterator[None]:
- try:
- in_transaction = connection.in_transaction # type: ignore[union-attr]
- except AttributeError:
- # catch for MockConnection, None
- in_transaction = None
- pass
-
- # yield outside the catch
- if in_transaction is None:
- yield
- else:
- if not in_transaction():
- assert connection is not None
- with connection.begin():
- yield
- else:
- yield
-
-
-def _safe_begin_connection_transaction(
- connection: Connection,
-) -> Transaction:
- transaction = connection.get_transaction()
- if transaction:
- return transaction
- else:
- return connection.begin()
-
-
-def _safe_commit_connection_transaction(
- connection: Connection,
-) -> None:
- transaction = connection.get_transaction()
- if transaction:
- transaction.commit()
-
-
-def _safe_rollback_connection_transaction(
- connection: Connection,
-) -> None:
- transaction = connection.get_transaction()
- if transaction:
- transaction.rollback()
-
-
-def _get_connection_in_transaction(connection: Optional[Connection]) -> bool:
- try:
- in_transaction = connection.in_transaction # type: ignore
- except AttributeError:
- # catch for MockConnection
- return False
- else:
- return in_transaction()
-
-
-def _idx_table_bound_expressions(idx: Index) -> Iterable[ColumnElement[Any]]:
- return idx.expressions # type: ignore
-
-
-def _copy(schema_item: _CE, **kw) -> _CE:
- if hasattr(schema_item, "_copy"):
- return schema_item._copy(**kw)
- else:
- return schema_item.copy(**kw) # type: ignore[union-attr]
-
-
-def _connectable_has_table(
- connectable: Connection, tablename: str, schemaname: Union[str, None]
-) -> bool:
- return connectable.dialect.has_table(connectable, tablename, schemaname)
-
-
-def _exec_on_inspector(inspector, statement, **params):
- with inspector._operation_context() as conn:
- return conn.execute(statement, params)
-
-
-def _nullability_might_be_unset(metadata_column):
- from sqlalchemy.sql import schema
-
- return metadata_column._user_defined_nullable is schema.NULL_UNSPECIFIED
-
-
-def _server_default_is_computed(*server_default) -> bool:
- return any(isinstance(sd, schema.Computed) for sd in server_default)
-
-
-def _server_default_is_identity(*server_default) -> bool:
- return any(isinstance(sd, schema.Identity) for sd in server_default)
-
-
-def _table_for_constraint(constraint: Constraint) -> Table:
- if isinstance(constraint, ForeignKeyConstraint):
- table = constraint.parent
- assert table is not None
- return table # type: ignore[return-value]
- else:
- return constraint.table
-
-
-def _columns_for_constraint(constraint):
- if isinstance(constraint, ForeignKeyConstraint):
- return [fk.parent for fk in constraint.elements]
- elif isinstance(constraint, CheckConstraint):
- return _find_columns(constraint.sqltext)
- else:
- return list(constraint.columns)
-
-
-def _resolve_for_variant(type_, dialect):
- if _type_has_variants(type_):
- base_type, mapping = _get_variant_mapping(type_)
- return mapping.get(dialect.name, base_type)
- else:
- return type_
-
-
-if hasattr(sqltypes.TypeEngine, "_variant_mapping"): # 2.0
-
- def _type_has_variants(type_):
- return bool(type_._variant_mapping)
-
- def _get_variant_mapping(type_):
- return type_, type_._variant_mapping
-
-else:
-
- def _type_has_variants(type_):
- return type(type_) is sqltypes.Variant
-
- def _get_variant_mapping(type_):
- return type_.impl, type_.mapping
-
-
-def _get_table_key(name: str, schema: Optional[str]) -> str:
- if schema is None:
- return name
- else:
- return schema + "." + name
-
-
-def _fk_spec(constraint: ForeignKeyConstraint) -> Any:
- if TYPE_CHECKING:
- assert constraint.columns is not None
- assert constraint.elements is not None
- assert isinstance(constraint.parent, Table)
-
- source_columns = [
- constraint.columns[key].name for key in constraint.column_keys
- ]
-
- source_table = constraint.parent.name
- source_schema = constraint.parent.schema
- target_schema = constraint.elements[0].column.table.schema
- target_table = constraint.elements[0].column.table.name
- target_columns = [element.column.name for element in constraint.elements]
- ondelete = constraint.ondelete
- onupdate = constraint.onupdate
- deferrable = constraint.deferrable
- initially = constraint.initially
- return (
- source_schema,
- source_table,
- source_columns,
- target_schema,
- target_table,
- target_columns,
- onupdate,
- ondelete,
- deferrable,
- initially,
- )
-
-
-def _fk_is_self_referential(constraint: ForeignKeyConstraint) -> bool:
- spec = constraint.elements[0]._get_colspec()
- tokens = spec.split(".")
- tokens.pop(-1) # colname
- tablekey = ".".join(tokens)
- assert constraint.parent is not None
- return tablekey == constraint.parent.key
-
-
-def _is_type_bound(constraint: Constraint) -> bool:
- # this deals with SQLAlchemy #3260, don't copy CHECK constraints
- # that will be generated by the type.
- # new feature added for #3260
- return constraint._type_bound
-
-
-def _find_columns(clause):
- """locate Column objects within the given expression."""
-
- cols: Set[ColumnElement[Any]] = set()
- traverse(clause, {}, {"column": cols.add})
- return cols
-
-
-def _remove_column_from_collection(
- collection: ColumnCollection, column: Union[Column[Any], ColumnClause[Any]]
-) -> None:
- """remove a column from a ColumnCollection."""
-
- # workaround for older SQLAlchemy, remove the
- # same object that's present
- assert column.key is not None
- to_remove = collection[column.key]
-
- # SQLAlchemy 2.0 will use more ReadOnlyColumnCollection
- # (renamed from ImmutableColumnCollection)
- if hasattr(collection, "_immutable") or hasattr(collection, "_readonly"):
- collection._parent.remove(to_remove)
- else:
- collection.remove(to_remove)
-
-
-def _textual_index_column(
- table: Table, text_: Union[str, TextClause, ColumnElement[Any]]
-) -> Union[ColumnElement[Any], Column[Any]]:
- """a workaround for the Index construct's severe lack of flexibility"""
- if isinstance(text_, str):
- c = Column(text_, sqltypes.NULLTYPE)
- table.append_column(c)
- return c
- elif isinstance(text_, TextClause):
- return _textual_index_element(table, text_)
- elif isinstance(text_, _textual_index_element):
- return _textual_index_column(table, text_.text)
- elif isinstance(text_, sql.ColumnElement):
- return _copy_expression(text_, table)
- else:
- raise ValueError("String or text() construct expected")
-
-
-def _copy_expression(expression: _CE, target_table: Table) -> _CE:
- def replace(col):
- if (
- isinstance(col, Column)
- and col.table is not None
- and col.table is not target_table
- ):
- if col.name in target_table.c:
- return target_table.c[col.name]
- else:
- c = _copy(col)
- target_table.append_column(c)
- return c
- else:
- return None
-
- return visitors.replacement_traverse( # type: ignore[call-overload]
- expression, {}, replace
- )
-
-
-class _textual_index_element(sql.ColumnElement):
- """Wrap around a sqlalchemy text() construct in such a way that
- we appear like a column-oriented SQL expression to an Index
- construct.
-
- The issue here is that currently the Postgresql dialect, the biggest
- recipient of functional indexes, keys all the index expressions to
- the corresponding column expressions when rendering CREATE INDEX,
- so the Index we create here needs to have a .columns collection that
- is the same length as the .expressions collection. Ultimately
- SQLAlchemy should support text() expressions in indexes.
-
- See SQLAlchemy issue 3174.
-
- """
-
- __visit_name__ = "_textual_idx_element"
-
- def __init__(self, table: Table, text: TextClause) -> None:
- self.table = table
- self.text = text
- self.key = text.text
- self.fake_column = schema.Column(self.text.text, sqltypes.NULLTYPE)
- table.append_column(self.fake_column)
-
- def get_children(self, **kw):
- return [self.fake_column]
-
-
-@compiles(_textual_index_element)
-def _render_textual_index_column(
- element: _textual_index_element, compiler: SQLCompiler, **kw
-) -> str:
- return compiler.process(element.text, **kw)
-
-
-class _literal_bindparam(BindParameter):
- pass
-
-
-@compiles(_literal_bindparam)
-def _render_literal_bindparam(
- element: _literal_bindparam, compiler: SQLCompiler, **kw
-) -> str:
- return compiler.render_literal_bindparam(element, **kw)
-
-
-def _get_constraint_final_name(
- constraint: Union[Index, Constraint], dialect: Optional[Dialect]
-) -> Optional[str]:
- if constraint.name is None:
- return None
- assert dialect is not None
- # for SQLAlchemy 1.4 we would like to have the option to expand
- # the use of "deferred" names for constraints as well as to have
- # some flexibility with "None" name and similar; make use of new
- # SQLAlchemy API to return what would be the final compiled form of
- # the name for this dialect.
- return dialect.identifier_preparer.format_constraint(
- constraint, _alembic_quote=False
- )
-
-
-def _constraint_is_named(
- constraint: Union[Constraint, Index], dialect: Optional[Dialect]
-) -> bool:
- if constraint.name is None:
- return False
- assert dialect is not None
- name = dialect.identifier_preparer.format_constraint(
- constraint, _alembic_quote=False
- )
- return name is not None
-
-
-def is_expression_index(index: Index) -> bool:
- for expr in index.expressions:
- if is_expression(expr):
- return True
- return False
-
-
-def is_expression(expr: Any) -> bool:
- while isinstance(expr, UnaryExpression):
- expr = expr.element
- if not isinstance(expr, ColumnClause) or expr.is_literal:
- return True
- return False
-
-
-def _inherit_schema_deprecated() -> bool:
- # at some point in 2.1 inherit_schema was replaced with a property
- # so that's preset at the class level, while before it wasn't.
- return sqla_2_1 and hasattr(sqltypes.Enum, "inherit_schema")
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA
deleted file mode 100644
index 9bf7a9e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA
+++ /dev/null
@@ -1,145 +0,0 @@
-Metadata-Version: 2.4
-Name: annotated-doc
-Version: 0.0.4
-Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
-Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?=
-License-Expression: MIT
-License-File: LICENSE
-Classifier: Intended Audience :: Information Technology
-Classifier: Intended Audience :: System Administrators
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python
-Classifier: Topic :: Internet
-Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Software Development :: Libraries
-Classifier: Topic :: Software Development
-Classifier: Typing :: Typed
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: 3.14
-Project-URL: Homepage, https://github.com/fastapi/annotated-doc
-Project-URL: Documentation, https://github.com/fastapi/annotated-doc
-Project-URL: Repository, https://github.com/fastapi/annotated-doc
-Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
-Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-
-# Annotated Doc
-
-Document parameters, class attributes, return types, and variables inline, with `Annotated`.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## Installation
-
-```bash
-pip install annotated-doc
-```
-
-Or with `uv`:
-
-```Python
-uv add annotated-doc
-```
-
-## Usage
-
-Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
-
-For example, to document a parameter `name` in a function `hi` you could do:
-
-```Python
-from typing import Annotated
-
-from annotated_doc import Doc
-
-def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
- print(f"Hi, {name}!")
-```
-
-You can also use it to document class attributes:
-
-```Python
-from typing import Annotated
-
-from annotated_doc import Doc
-
-class User:
- name: Annotated[str, Doc("The user's name")]
- age: Annotated[int, Doc("The user's age")]
-```
-
-The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
-
-## Who Uses This
-
-`annotated-doc` was made for:
-
-* [FastAPI](https://fastapi.tiangolo.com/)
-* [Typer](https://typer.tiangolo.com/)
-* [SQLModel](https://sqlmodel.tiangolo.com/)
-* [Asyncer](https://asyncer.tiangolo.com/)
-
-`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/).
-
-## Reasons not to use `annotated-doc`
-
-You are already comfortable with one of the existing docstring formats, like:
-
-* Sphinx
-* numpydoc
-* Google
-* Keras
-
-Your team is already comfortable using them.
-
-You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
-
-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.
-
-## Reasons to use `annotated-doc`
-
-* No micro-syntax to learn for newcomers, it’s **just Python** syntax.
-* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
-* **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.
-* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
-* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
-* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
-* **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.
-* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
-* A more formalized way to document other symbols, like type aliases, that could use Annotated.
-* **Support** for apps using FastAPI, Typer and others.
-* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
-
-## History
-
-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.
-
-The conclusion was that this was better done as an external effort, in a third-party library.
-
-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.
-
-## License
-
-This project is licensed under the terms of the MIT license.
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD
deleted file mode 100644
index 06bbc8d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD
+++ /dev/null
@@ -1,11 +0,0 @@
-annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
-annotated_doc-0.0.4.dist-info/RECORD,,
-annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
-annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
-annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
-annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
-annotated_doc/__pycache__/__init__.cpython-312.pyc,,
-annotated_doc/__pycache__/main.cpython-312.pyc,,
-annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
-annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL
deleted file mode 100644
index 045c8ac..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: pdm-backend (2.4.5)
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt
deleted file mode 100644
index c3ad472..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-[console_scripts]
-
-[gui_scripts]
-
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE
deleted file mode 100644
index 7a25446..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2025 Sebastián Ramírez
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__init__.py b/backend/.venv/lib/python3.12/site-packages/annotated_doc/__init__.py
deleted file mode 100644
index a0152a7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .main import Doc as Doc
-
-__version__ = "0.0.4"
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index acef568..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc
deleted file mode 100644
index 3f27881..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc/main.py b/backend/.venv/lib/python3.12/site-packages/annotated_doc/main.py
deleted file mode 100644
index 7063c59..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_doc/main.py
+++ /dev/null
@@ -1,36 +0,0 @@
-class Doc:
- """Define the documentation of a type annotation using `Annotated`, to be
- used in class attributes, function and method parameters, return values,
- and variables.
-
- The value should be a positional-only string literal to allow static tools
- like editors and documentation generators to use it.
-
- This complements docstrings.
-
- The string value passed is available in the attribute `documentation`.
-
- Example:
-
- ```Python
- from typing import Annotated
- from annotated_doc import Doc
-
- def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
- print(f"Hi, {name}!")
- ```
- """
-
- def __init__(self, documentation: str, /) -> None:
- self.documentation = documentation
-
- def __repr__(self) -> str:
- return f"Doc({self.documentation!r})"
-
- def __hash__(self) -> int:
- return hash(self.documentation)
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, Doc):
- return NotImplemented
- return self.documentation == other.documentation
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_doc/py.typed b/backend/.venv/lib/python3.12/site-packages/annotated_doc/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA
deleted file mode 100644
index 3ac05cf..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA
+++ /dev/null
@@ -1,295 +0,0 @@
-Metadata-Version: 2.3
-Name: annotated-types
-Version: 0.7.0
-Summary: Reusable constraint types to use with typing.Annotated
-Project-URL: Homepage, https://github.com/annotated-types/annotated-types
-Project-URL: Source, https://github.com/annotated-types/annotated-types
-Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
-Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin , Zac Hatfield-Dodds
-License-File: LICENSE
-Classifier: Development Status :: 4 - Beta
-Classifier: Environment :: Console
-Classifier: Environment :: MacOS X
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: Information Technology
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: POSIX :: Linux
-Classifier: Operating System :: Unix
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Typing :: Typed
-Requires-Python: >=3.8
-Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
-Description-Content-Type: text/markdown
-
-# annotated-types
-
-[](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
-[](https://pypi.python.org/pypi/annotated-types)
-[](https://github.com/annotated-types/annotated-types)
-[](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
-
-[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
-adding context-specific metadata to existing types, and specifies that
-`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
-logic for `x`.
-
-This package provides metadata objects which can be used to represent common
-constraints such as upper and lower bounds on scalar values and collection sizes,
-a `Predicate` marker for runtime checks, and
-descriptions of how we intend these metadata to be interpreted. In some cases,
-we also note alternative representations which do not require this package.
-
-## Install
-
-```bash
-pip install annotated-types
-```
-
-## Examples
-
-```python
-from typing import Annotated
-from annotated_types import Gt, Len, Predicate
-
-class MyClass:
- age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
- # Invalid: 17, 18, "19", 19.0, ...
- factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
- # Invalid: 4, 8, -2, 5.0, "prime", ...
-
- my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
- # Invalid: (1, 2), ["abc"], [0] * 20
-```
-
-## Documentation
-
-_While `annotated-types` avoids runtime checks for performance, users should not
-construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
-Downstream implementors may choose to raise an error, emit a warning, silently ignore
-a metadata item, etc., if the metadata objects described below are used with an
-incompatible type - or for any other reason!_
-
-### Gt, Ge, Lt, Le
-
-Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
-dates, times, strings, sets, etc. Note that the boundary value need not be of the
-same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
-is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
-
-We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
-as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
-the `annotated-types` package.
-
-To be explicit, these types have the following meanings:
-
-* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
-* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
-* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
-* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
-
-### Interval
-
-`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
-metadata object. `None` attributes should be ignored, and non-`None` attributes
-treated as per the single bounds above.
-
-### MultipleOf
-
-`MultipleOf(multiple_of=x)` might be interpreted in two ways:
-
-1. Python semantics, implying `value % multiple_of == 0`, or
-2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
- where `int(value / multiple_of) == value / multiple_of`.
-
-We encourage users to be aware of these two common interpretations and their
-distinct behaviours, especially since very large or non-integer numbers make
-it easy to cause silent data corruption due to floating-point imprecision.
-
-We encourage libraries to carefully document which interpretation they implement.
-
-### MinLen, MaxLen, Len
-
-`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
-
-As well as `Len()` which can optionally include upper and lower bounds, we also
-provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
-and `Len(max_length=y)` respectively.
-
-`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
-
-Examples of usage:
-
-* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
-* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
-* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
-* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
-* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
-
-#### Changed in v0.4.0
-
-* `min_inclusive` has been renamed to `min_length`, no change in meaning
-* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
-* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
- meaning of the upper bound in slices vs. `Len`
-
-See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
-
-### Timezone
-
-`Timezone` can be used with a `datetime` or a `time` to express which timezones
-are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
-`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
-expresses that any timezone-aware datetime is allowed. You may also pass a specific
-timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
-object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
-allow a specific timezone, though we note that this is often a symptom of fragile design.
-
-#### Changed in v0.x.x
-
-* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
- `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
-
-### Unit
-
-`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
-a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
-would be a float representing a velocity in meters per second.
-
-Please note that `annotated_types` itself makes no attempt to parse or validate
-the unit string in any way. That is left entirely to downstream libraries,
-such as [`pint`](https://pint.readthedocs.io) or
-[`astropy.units`](https://docs.astropy.org/en/stable/units/).
-
-An example of how a library might use this metadata:
-
-```python
-from annotated_types import Unit
-from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
-
-# given a type annotated with a unit:
-Meters = Annotated[float, Unit("m")]
-
-
-# you can cast the annotation to a specific unit type with any
-# callable that accepts a string and returns the desired type
-T = TypeVar("T")
-def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
- if get_origin(tp) is Annotated:
- for arg in get_args(tp):
- if isinstance(arg, Unit):
- return unit_cls(arg.unit)
- return None
-
-
-# using `pint`
-import pint
-pint_unit = cast_unit(Meters, pint.Unit)
-
-
-# using `astropy.units`
-import astropy.units as u
-astropy_unit = cast_unit(Meters, u.Unit)
-```
-
-### Predicate
-
-`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
-Users should prefer the statically inspectable metadata above, but if you need
-the full power and flexibility of arbitrary runtime predicates... here it is.
-
-For some common constraints, we provide generic types:
-
-* `IsLower = Annotated[T, Predicate(str.islower)]`
-* `IsUpper = Annotated[T, Predicate(str.isupper)]`
-* `IsDigit = Annotated[T, Predicate(str.isdigit)]`
-* `IsFinite = Annotated[T, Predicate(math.isfinite)]`
-* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
-* `IsNan = Annotated[T, Predicate(math.isnan)]`
-* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
-* `IsInfinite = Annotated[T, Predicate(math.isinf)]`
-* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
-
-so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
-(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
-
-Some libraries might have special logic to handle known or understandable predicates,
-for example by checking for `str.isdigit` and using its presence to both call custom
-logic to enforce digit-only strings, and customise some generated external schema.
-Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
-favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
-
-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.
-
-We do not specify what behaviour should be expected for predicates that raise
-an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
-skip invalid constraints, or statically raise an error; or it might try calling it
-and then propagate or discard the resulting
-`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
-exception. We encourage libraries to document the behaviour they choose.
-
-### Doc
-
-`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.
-
-It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
-
-It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
-
-This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
-
-### Integrating downstream types with `GroupedMetadata`
-
-Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
-This can help reduce verbosity and cognitive overhead for users.
-For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
-
-```python
-from dataclasses import dataclass
-from typing import Iterator
-from annotated_types import GroupedMetadata, Ge
-
-@dataclass
-class Field(GroupedMetadata):
- ge: int | None = None
- description: str | None = None
-
- def __iter__(self) -> Iterator[object]:
- # Iterating over a GroupedMetadata object should yield annotated-types
- # constraint metadata objects which describe it as fully as possible,
- # and may include other unknown objects too.
- if self.ge is not None:
- yield Ge(self.ge)
- if self.description is not None:
- yield Description(self.description)
-```
-
-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.
-
-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.
-
-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`.
-
-### Consuming metadata
-
-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).
-
-It is up to the implementer to determine how this metadata is used.
-You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
-
-## Design & History
-
-This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
-and Hypothesis, with the goal of making it as easy as possible for end-users to
-provide more informative annotations for use by runtime libraries.
-
-It is deliberately minimal, and following PEP-593 allows considerable downstream
-discretion in what (if anything!) they choose to support. Nonetheless, we expect
-that staying simple and covering _only_ the most common use-cases will give users
-and maintainers the best experience we can. If you'd like more constraints for your
-types - follow our lead, by defining them and documenting them downstream!
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD
deleted file mode 100644
index 7045729..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD
+++ /dev/null
@@ -1,10 +0,0 @@
-annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
-annotated_types-0.7.0.dist-info/RECORD,,
-annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
-annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
-annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
-annotated_types/__pycache__/__init__.cpython-312.pyc,,
-annotated_types/__pycache__/test_cases.cpython-312.pyc,,
-annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL
deleted file mode 100644
index 516596c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: hatchling 1.24.2
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE
deleted file mode 100644
index d99323a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2022 the contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types/__init__.py b/backend/.venv/lib/python3.12/site-packages/annotated_types/__init__.py
deleted file mode 100644
index 74e0dee..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types/__init__.py
+++ /dev/null
@@ -1,432 +0,0 @@
-import math
-import sys
-import types
-from dataclasses import dataclass
-from datetime import tzinfo
-from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
-
-if sys.version_info < (3, 8):
- from typing_extensions import Protocol, runtime_checkable
-else:
- from typing import Protocol, runtime_checkable
-
-if sys.version_info < (3, 9):
- from typing_extensions import Annotated, Literal
-else:
- from typing import Annotated, Literal
-
-if sys.version_info < (3, 10):
- EllipsisType = type(Ellipsis)
- KW_ONLY = {}
- SLOTS = {}
-else:
- from types import EllipsisType
-
- KW_ONLY = {"kw_only": True}
- SLOTS = {"slots": True}
-
-
-__all__ = (
- 'BaseMetadata',
- 'GroupedMetadata',
- 'Gt',
- 'Ge',
- 'Lt',
- 'Le',
- 'Interval',
- 'MultipleOf',
- 'MinLen',
- 'MaxLen',
- 'Len',
- 'Timezone',
- 'Predicate',
- 'LowerCase',
- 'UpperCase',
- 'IsDigits',
- 'IsFinite',
- 'IsNotFinite',
- 'IsNan',
- 'IsNotNan',
- 'IsInfinite',
- 'IsNotInfinite',
- 'doc',
- 'DocInfo',
- '__version__',
-)
-
-__version__ = '0.7.0'
-
-
-T = TypeVar('T')
-
-
-# arguments that start with __ are considered
-# positional only
-# see https://peps.python.org/pep-0484/#positional-only-arguments
-
-
-class SupportsGt(Protocol):
- def __gt__(self: T, __other: T) -> bool:
- ...
-
-
-class SupportsGe(Protocol):
- def __ge__(self: T, __other: T) -> bool:
- ...
-
-
-class SupportsLt(Protocol):
- def __lt__(self: T, __other: T) -> bool:
- ...
-
-
-class SupportsLe(Protocol):
- def __le__(self: T, __other: T) -> bool:
- ...
-
-
-class SupportsMod(Protocol):
- def __mod__(self: T, __other: T) -> T:
- ...
-
-
-class SupportsDiv(Protocol):
- def __div__(self: T, __other: T) -> T:
- ...
-
-
-class BaseMetadata:
- """Base class for all metadata.
-
- This exists mainly so that implementers
- can do `isinstance(..., BaseMetadata)` while traversing field annotations.
- """
-
- __slots__ = ()
-
-
-@dataclass(frozen=True, **SLOTS)
-class Gt(BaseMetadata):
- """Gt(gt=x) implies that the value must be greater than x.
-
- It can be used with any type that supports the ``>`` operator,
- including numbers, dates and times, strings, sets, and so on.
- """
-
- gt: SupportsGt
-
-
-@dataclass(frozen=True, **SLOTS)
-class Ge(BaseMetadata):
- """Ge(ge=x) implies that the value must be greater than or equal to x.
-
- It can be used with any type that supports the ``>=`` operator,
- including numbers, dates and times, strings, sets, and so on.
- """
-
- ge: SupportsGe
-
-
-@dataclass(frozen=True, **SLOTS)
-class Lt(BaseMetadata):
- """Lt(lt=x) implies that the value must be less than x.
-
- It can be used with any type that supports the ``<`` operator,
- including numbers, dates and times, strings, sets, and so on.
- """
-
- lt: SupportsLt
-
-
-@dataclass(frozen=True, **SLOTS)
-class Le(BaseMetadata):
- """Le(le=x) implies that the value must be less than or equal to x.
-
- It can be used with any type that supports the ``<=`` operator,
- including numbers, dates and times, strings, sets, and so on.
- """
-
- le: SupportsLe
-
-
-@runtime_checkable
-class GroupedMetadata(Protocol):
- """A grouping of multiple objects, like typing.Unpack.
-
- `GroupedMetadata` on its own is not metadata and has no meaning.
- All of the constraints and metadata should be fully expressable
- in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
-
- Concrete implementations should override `GroupedMetadata.__iter__()`
- to add their own metadata.
- For example:
-
- >>> @dataclass
- >>> class Field(GroupedMetadata):
- >>> gt: float | None = None
- >>> description: str | None = None
- ...
- >>> def __iter__(self) -> Iterable[object]:
- >>> if self.gt is not None:
- >>> yield Gt(self.gt)
- >>> if self.description is not None:
- >>> yield Description(self.gt)
-
- Also see the implementation of `Interval` below for an example.
-
- Parsers should recognize this and unpack it so that it can be used
- both with and without unpacking:
-
- - `Annotated[int, Field(...)]` (parser must unpack Field)
- - `Annotated[int, *Field(...)]` (PEP-646)
- """ # noqa: trailing-whitespace
-
- @property
- def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
- return True
-
- def __iter__(self) -> Iterator[object]:
- ...
-
- if not TYPE_CHECKING:
- __slots__ = () # allow subclasses to use slots
-
- def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
- # Basic ABC like functionality without the complexity of an ABC
- super().__init_subclass__(*args, **kwargs)
- if cls.__iter__ is GroupedMetadata.__iter__:
- raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
-
- def __iter__(self) -> Iterator[object]: # noqa: F811
- raise NotImplementedError # more helpful than "None has no attribute..." type errors
-
-
-@dataclass(frozen=True, **KW_ONLY, **SLOTS)
-class Interval(GroupedMetadata):
- """Interval can express inclusive or exclusive bounds with a single object.
-
- It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
- are interpreted the same way as the single-bound constraints.
- """
-
- gt: Union[SupportsGt, None] = None
- ge: Union[SupportsGe, None] = None
- lt: Union[SupportsLt, None] = None
- le: Union[SupportsLe, None] = None
-
- def __iter__(self) -> Iterator[BaseMetadata]:
- """Unpack an Interval into zero or more single-bounds."""
- if self.gt is not None:
- yield Gt(self.gt)
- if self.ge is not None:
- yield Ge(self.ge)
- if self.lt is not None:
- yield Lt(self.lt)
- if self.le is not None:
- yield Le(self.le)
-
-
-@dataclass(frozen=True, **SLOTS)
-class MultipleOf(BaseMetadata):
- """MultipleOf(multiple_of=x) might be interpreted in two ways:
-
- 1. Python semantics, implying ``value % multiple_of == 0``, or
- 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
-
- We encourage users to be aware of these two common interpretations,
- and libraries to carefully document which they implement.
- """
-
- multiple_of: Union[SupportsDiv, SupportsMod]
-
-
-@dataclass(frozen=True, **SLOTS)
-class MinLen(BaseMetadata):
- """
- MinLen() implies minimum inclusive length,
- e.g. ``len(value) >= min_length``.
- """
-
- min_length: Annotated[int, Ge(0)]
-
-
-@dataclass(frozen=True, **SLOTS)
-class MaxLen(BaseMetadata):
- """
- MaxLen() implies maximum inclusive length,
- e.g. ``len(value) <= max_length``.
- """
-
- max_length: Annotated[int, Ge(0)]
-
-
-@dataclass(frozen=True, **SLOTS)
-class Len(GroupedMetadata):
- """
- Len() implies that ``min_length <= len(value) <= max_length``.
-
- Upper bound may be omitted or ``None`` to indicate no upper length bound.
- """
-
- min_length: Annotated[int, Ge(0)] = 0
- max_length: Optional[Annotated[int, Ge(0)]] = None
-
- def __iter__(self) -> Iterator[BaseMetadata]:
- """Unpack a Len into zone or more single-bounds."""
- if self.min_length > 0:
- yield MinLen(self.min_length)
- if self.max_length is not None:
- yield MaxLen(self.max_length)
-
-
-@dataclass(frozen=True, **SLOTS)
-class Timezone(BaseMetadata):
- """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
-
- ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
- ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
- tz-aware but any timezone is allowed.
-
- You may also pass a specific timezone string or tzinfo object such as
- ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
- you only allow a specific timezone, though we note that this is often
- a symptom of poor design.
- """
-
- tz: Union[str, tzinfo, EllipsisType, None]
-
-
-@dataclass(frozen=True, **SLOTS)
-class Unit(BaseMetadata):
- """Indicates that the value is a physical quantity with the specified unit.
-
- It is intended for usage with numeric types, where the value represents the
- magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
- or ``speed: Annotated[float, Unit('m/s')]``.
-
- Interpretation of the unit string is left to the discretion of the consumer.
- It is suggested to follow conventions established by python libraries that work
- with physical quantities, such as
-
- - ``pint`` :
- - ``astropy.units``:
-
- For indicating a quantity with a certain dimensionality but without a specific unit
- it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
- Note, however, ``annotated_types`` itself makes no use of the unit string.
- """
-
- unit: str
-
-
-@dataclass(frozen=True, **SLOTS)
-class Predicate(BaseMetadata):
- """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
-
- Users should prefer statically inspectable metadata, but if you need the full
- power and flexibility of arbitrary runtime predicates... here it is.
-
- We provide a few predefined predicates for common string constraints:
- ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
- ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
- can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
-
- Some libraries might have special logic to handle certain predicates, e.g. by
- checking for `str.isdigit` and using its presence to both call custom logic to
- enforce digit-only strings, and customise some generated external schema.
-
- We do not specify what behaviour should be expected for predicates that raise
- an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
- skip invalid constraints, or statically raise an error; or it might try calling it
- and then propagate or discard the resulting exception.
- """
-
- func: Callable[[Any], bool]
-
- def __repr__(self) -> str:
- if getattr(self.func, "__name__", "") == "":
- return f"{self.__class__.__name__}({self.func!r})"
- if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
- namespace := getattr(self.func.__self__, "__name__", None)
- ):
- return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
- if isinstance(self.func, type(str.isascii)): # method descriptor
- return f"{self.__class__.__name__}({self.func.__qualname__})"
- return f"{self.__class__.__name__}({self.func.__name__})"
-
-
-@dataclass
-class Not:
- func: Callable[[Any], bool]
-
- def __call__(self, __v: Any) -> bool:
- return not self.func(__v)
-
-
-_StrType = TypeVar("_StrType", bound=str)
-
-LowerCase = Annotated[_StrType, Predicate(str.islower)]
-"""
-Return True if the string is a lowercase string, False otherwise.
-
-A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
-""" # noqa: E501
-UpperCase = Annotated[_StrType, Predicate(str.isupper)]
-"""
-Return True if the string is an uppercase string, False otherwise.
-
-A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
-""" # noqa: E501
-IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
-IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
-"""
-Return True if the string is a digit string, False otherwise.
-
-A string is a digit string if all characters in the string are digits and there is at least one character in the string.
-""" # noqa: E501
-IsAscii = Annotated[_StrType, Predicate(str.isascii)]
-"""
-Return True if all characters in the string are ASCII, False otherwise.
-
-ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
-"""
-
-_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
-IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
-"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
-IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
-"""Return True if x is one of infinity or NaN, and False otherwise"""
-IsNan = Annotated[_NumericType, Predicate(math.isnan)]
-"""Return True if x is a NaN (not a number), and False otherwise."""
-IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
-"""Return True if x is anything but NaN (not a number), and False otherwise."""
-IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
-"""Return True if x is a positive or negative infinity, and False otherwise."""
-IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
-"""Return True if x is neither a positive or negative infinity, and False otherwise."""
-
-try:
- from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
-except ImportError:
-
- @dataclass(frozen=True, **SLOTS)
- class DocInfo: # type: ignore [no-redef]
- """ "
- The return value of doc(), mainly to be used by tools that want to extract the
- Annotated documentation at runtime.
- """
-
- documentation: str
- """The documentation string passed to doc()."""
-
- def doc(
- documentation: str,
- ) -> DocInfo:
- """
- Add documentation to a type annotation inside of Annotated.
-
- For example:
-
- >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
- """
- return DocInfo(documentation)
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 7351c62..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc
deleted file mode 100644
index 77f1917..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types/py.typed b/backend/.venv/lib/python3.12/site-packages/annotated_types/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py b/backend/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py
deleted file mode 100644
index d9164d6..0000000
--- a/backend/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py
+++ /dev/null
@@ -1,151 +0,0 @@
-import math
-import sys
-from datetime import date, datetime, timedelta, timezone
-from decimal import Decimal
-from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
-
-if sys.version_info < (3, 9):
- from typing_extensions import Annotated
-else:
- from typing import Annotated
-
-import annotated_types as at
-
-
-class Case(NamedTuple):
- """
- A test case for `annotated_types`.
- """
-
- annotation: Any
- valid_cases: Iterable[Any]
- invalid_cases: Iterable[Any]
-
-
-def cases() -> Iterable[Case]:
- # Gt, Ge, Lt, Le
- yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
- yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
- yield Case(
- Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
- [datetime(2000, 1, 2), datetime(2000, 1, 3)],
- [datetime(2000, 1, 1), datetime(1999, 12, 31)],
- )
- yield Case(
- Annotated[datetime, at.Gt(date(2000, 1, 1))],
- [date(2000, 1, 2), date(2000, 1, 3)],
- [date(2000, 1, 1), date(1999, 12, 31)],
- )
- yield Case(
- Annotated[datetime, at.Gt(Decimal('1.123'))],
- [Decimal('1.1231'), Decimal('123')],
- [Decimal('1.123'), Decimal('0')],
- )
-
- yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
- yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
- yield Case(
- Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
- [datetime(2000, 1, 2), datetime(2000, 1, 3)],
- [datetime(1998, 1, 1), datetime(1999, 12, 31)],
- )
-
- yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
- yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
- yield Case(
- Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
- [datetime(1999, 12, 31), datetime(1999, 12, 31)],
- [datetime(2000, 1, 2), datetime(2000, 1, 3)],
- )
-
- yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
- yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
- yield Case(
- Annotated[datetime, at.Le(datetime(2000, 1, 1))],
- [datetime(2000, 1, 1), datetime(1999, 12, 31)],
- [datetime(2000, 1, 2), datetime(2000, 1, 3)],
- )
-
- # Interval
- yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
- yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
- yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
- yield Case(
- Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
- [datetime(2000, 1, 2), datetime(2000, 1, 3)],
- [datetime(2000, 1, 1), datetime(2000, 1, 4)],
- )
-
- yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
- yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
-
- # lengths
-
- yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
- yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
- yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
- yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
-
- yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
- yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
- yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
- yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
-
- yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
- yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
-
- yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
- yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
- yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
-
- # Timezone
-
- yield Case(
- Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
- )
- yield Case(
- Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
- )
- yield Case(
- Annotated[datetime, at.Timezone(timezone.utc)],
- [datetime(2000, 1, 1, tzinfo=timezone.utc)],
- [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
- )
- yield Case(
- Annotated[datetime, at.Timezone('Europe/London')],
- [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
- [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
- )
-
- # Quantity
-
- yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
-
- # predicate types
-
- yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
- yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
- yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
- yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
-
- yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
-
- yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
- yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
- yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
- yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
- yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
- yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
-
- # check stacked predicates
- yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
-
- # doc
- yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
-
- # custom GroupedMetadata
- class MyCustomGroupedMetadata(at.GroupedMetadata):
- def __iter__(self) -> Iterator[at.Predicate]:
- yield at.Predicate(lambda x: float(x).is_integer())
-
- yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA
deleted file mode 100644
index dbeb198..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA
+++ /dev/null
@@ -1,96 +0,0 @@
-Metadata-Version: 2.4
-Name: anyio
-Version: 4.12.1
-Summary: High-level concurrency and networking framework on top of asyncio or Trio
-Author-email: Alex Grönholm
-License-Expression: MIT
-Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
-Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
-Project-URL: Source code, https://github.com/agronholm/anyio
-Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Framework :: AnyIO
-Classifier: Typing :: Typed
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: 3.14
-Requires-Python: >=3.9
-Description-Content-Type: text/x-rst
-License-File: LICENSE
-Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
-Requires-Dist: idna>=2.8
-Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
-Provides-Extra: trio
-Requires-Dist: trio>=0.32.0; python_version >= "3.10" and extra == "trio"
-Requires-Dist: trio>=0.31.0; python_version < "3.10" and extra == "trio"
-Dynamic: license-file
-
-.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
- :target: https://github.com/agronholm/anyio/actions/workflows/test.yml
- :alt: Build Status
-.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
- :target: https://coveralls.io/github/agronholm/anyio?branch=master
- :alt: Code Coverage
-.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
- :target: https://anyio.readthedocs.io/en/latest/?badge=latest
- :alt: Documentation
-.. image:: https://badges.gitter.im/gitterHQ/gitter.svg
- :target: https://gitter.im/python-trio/AnyIO
- :alt: Gitter chat
-
-AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
-Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
-with the native SC of Trio itself.
-
-Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
-Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
-refactoring necessary. It will blend in with the native libraries of your chosen backend.
-
-To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
-`here `_.
-
-Documentation
--------------
-
-View full documentation at: https://anyio.readthedocs.io/
-
-Features
---------
-
-AnyIO offers the following functionality:
-
-* Task groups (nurseries_ in trio terminology)
-* High-level networking (TCP, UDP and UNIX sockets)
-
- * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
- 3.8)
- * async/await style UDP sockets (unlike asyncio where you still have to use Transports and
- Protocols)
-
-* A versatile API for byte streams and object streams
-* Inter-task synchronization and communication (locks, conditions, events, semaphores, object
- streams)
-* Worker threads
-* Subprocesses
-* Subinterpreter support for code parallelization (on Python 3.13 and later)
-* Asynchronous file I/O (using worker threads)
-* Signal handling
-* Asynchronous version of the functools_ module
-
-AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
-It even works with the popular Hypothesis_ library.
-
-.. _asyncio: https://docs.python.org/3/library/asyncio.html
-.. _Trio: https://github.com/python-trio/trio
-.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
-.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
-.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
-.. _pytest: https://docs.pytest.org/en/latest/
-.. _functools: https://docs.python.org/3/library/functools.html
-.. _Hypothesis: https://hypothesis.works/
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD
deleted file mode 100644
index 9be42bb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD
+++ /dev/null
@@ -1,92 +0,0 @@
-anyio-4.12.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-anyio-4.12.1.dist-info/METADATA,sha256=DfiDab9Tmmcfy802lOLTMEHJQShkOSbopCwqCYbLuJk,4277
-anyio-4.12.1.dist-info/RECORD,,
-anyio-4.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
-anyio-4.12.1.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
-anyio-4.12.1.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
-anyio-4.12.1.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
-anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170
-anyio/__pycache__/__init__.cpython-312.pyc,,
-anyio/__pycache__/from_thread.cpython-312.pyc,,
-anyio/__pycache__/functools.cpython-312.pyc,,
-anyio/__pycache__/lowlevel.cpython-312.pyc,,
-anyio/__pycache__/pytest_plugin.cpython-312.pyc,,
-anyio/__pycache__/to_interpreter.cpython-312.pyc,,
-anyio/__pycache__/to_process.cpython-312.pyc,,
-anyio/__pycache__/to_thread.cpython-312.pyc,,
-anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-anyio/_backends/__pycache__/__init__.cpython-312.pyc,,
-anyio/_backends/__pycache__/_asyncio.cpython-312.pyc,,
-anyio/_backends/__pycache__/_trio.cpython-312.pyc,,
-anyio/_backends/_asyncio.py,sha256=xG6qv60mgGnL0mK82dxjH2b8hlkMlJ-x2BqIq3qv70Y,98863
-anyio/_backends/_trio.py,sha256=30Rctb7lm8g63ZHljVPVnj5aH-uK6oQvphjwUBoAzuI,41456
-anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-anyio/_core/__pycache__/__init__.cpython-312.pyc,,
-anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc,,
-anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc,,
-anyio/_core/__pycache__/_eventloop.cpython-312.pyc,,
-anyio/_core/__pycache__/_exceptions.cpython-312.pyc,,
-anyio/_core/__pycache__/_fileio.cpython-312.pyc,,
-anyio/_core/__pycache__/_resources.cpython-312.pyc,,
-anyio/_core/__pycache__/_signals.cpython-312.pyc,,
-anyio/_core/__pycache__/_sockets.cpython-312.pyc,,
-anyio/_core/__pycache__/_streams.cpython-312.pyc,,
-anyio/_core/__pycache__/_subprocesses.cpython-312.pyc,,
-anyio/_core/__pycache__/_synchronization.cpython-312.pyc,,
-anyio/_core/__pycache__/_tasks.cpython-312.pyc,,
-anyio/_core/__pycache__/_tempfile.cpython-312.pyc,,
-anyio/_core/__pycache__/_testing.cpython-312.pyc,,
-anyio/_core/__pycache__/_typedattr.cpython-312.pyc,,
-anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
-anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
-anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448
-anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407
-anyio/_core/_fileio.py,sha256=uc7t10Vb-If7GbdWM_zFf-ajUe6uek63fSt7IBLlZW0,25731
-anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
-anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
-anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977
-anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
-anyio/_core/_subprocesses.py,sha256=EXm5igL7dj55iYkPlbYVAqtbqxJxjU-6OndSTIx9SRg,8047
-anyio/_core/_synchronization.py,sha256=MgVVqFzvt580tHC31LiOcq1G6aryut--xRG4Ff8KwxQ,20869
-anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435
-anyio/_core/_tempfile.py,sha256=lHb7CW4FyIlpkf5ADAf4VmLHCKwEHF9nxqNyBCFFUiA,19697
-anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
-anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
-anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
-anyio/abc/__pycache__/__init__.cpython-312.pyc,,
-anyio/abc/__pycache__/_eventloop.cpython-312.pyc,,
-anyio/abc/__pycache__/_resources.cpython-312.pyc,,
-anyio/abc/__pycache__/_sockets.cpython-312.pyc,,
-anyio/abc/__pycache__/_streams.cpython-312.pyc,,
-anyio/abc/__pycache__/_subprocesses.cpython-312.pyc,,
-anyio/abc/__pycache__/_tasks.cpython-312.pyc,,
-anyio/abc/__pycache__/_testing.cpython-312.pyc,,
-anyio/abc/_eventloop.py,sha256=GlzgB3UJGgG6Kr7olpjOZ-o00PghecXuofVDQ_5611Q,10749
-anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
-anyio/abc/_sockets.py,sha256=ECTY0jLEF18gryANHR3vFzXzGdZ-xPwELq1QdgOb0Jo,13258
-anyio/abc/_streams.py,sha256=005GKSCXGprxnhucILboSqc2JFovECZk9m3p-qqxXVc,7640
-anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
-anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721
-anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821
-anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141
-anyio/functools.py,sha256=HWj7GBEmc0Z-mZg3uok7Z7ZJn0rEC_0Pzbt0nYUDaTQ,10973
-anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158
-anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-anyio/pytest_plugin.py,sha256=3jAFQn0jv_pyoWE2GBBlHaj9sqXj4e8vob0_hgrsXE8,10244
-anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-anyio/streams/__pycache__/__init__.cpython-312.pyc,,
-anyio/streams/__pycache__/buffered.cpython-312.pyc,,
-anyio/streams/__pycache__/file.cpython-312.pyc,,
-anyio/streams/__pycache__/memory.cpython-312.pyc,,
-anyio/streams/__pycache__/stapled.cpython-312.pyc,,
-anyio/streams/__pycache__/text.cpython-312.pyc,,
-anyio/streams/__pycache__/tls.cpython-312.pyc,,
-anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263
-anyio/streams/file.py,sha256=4WZ7XGz5WNu39FQHvqbe__TQ0HDP9OOhgO1mk9iVpVU,4470
-anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740
-anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390
-anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
-anyio/streams/tls.py,sha256=Jpxy0Mfbcp1BxHCwE-YjSSFaLnIBbnnwur-excYThs4,15368
-anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
-anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798
-anyio/to_thread.py,sha256=menEgXYmUV7Fjg_9WqCV95P9MAtQS8BzPGGcWB_QnfQ,2687
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL
deleted file mode 100644
index e7fa31b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: setuptools (80.9.0)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt
deleted file mode 100644
index 44dd9bd..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[pytest11]
-anyio = anyio.pytest_plugin
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE
deleted file mode 100644
index 104eebf..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2018 Alex Grönholm
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt b/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt
deleted file mode 100644
index c77c069..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-anyio
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__init__.py b/backend/.venv/lib/python3.12/site-packages/anyio/__init__.py
deleted file mode 100644
index d23c5a5..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/__init__.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from __future__ import annotations
-
-from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
-from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
-from ._core._eventloop import current_time as current_time
-from ._core._eventloop import get_all_backends as get_all_backends
-from ._core._eventloop import get_available_backends as get_available_backends
-from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
-from ._core._eventloop import run as run
-from ._core._eventloop import sleep as sleep
-from ._core._eventloop import sleep_forever as sleep_forever
-from ._core._eventloop import sleep_until as sleep_until
-from ._core._exceptions import BrokenResourceError as BrokenResourceError
-from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
-from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
-from ._core._exceptions import BusyResourceError as BusyResourceError
-from ._core._exceptions import ClosedResourceError as ClosedResourceError
-from ._core._exceptions import ConnectionFailed as ConnectionFailed
-from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
-from ._core._exceptions import EndOfStream as EndOfStream
-from ._core._exceptions import IncompleteRead as IncompleteRead
-from ._core._exceptions import NoEventLoopError as NoEventLoopError
-from ._core._exceptions import RunFinishedError as RunFinishedError
-from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
-from ._core._exceptions import WouldBlock as WouldBlock
-from ._core._fileio import AsyncFile as AsyncFile
-from ._core._fileio import Path as Path
-from ._core._fileio import open_file as open_file
-from ._core._fileio import wrap_file as wrap_file
-from ._core._resources import aclose_forcefully as aclose_forcefully
-from ._core._signals import open_signal_receiver as open_signal_receiver
-from ._core._sockets import TCPConnectable as TCPConnectable
-from ._core._sockets import UNIXConnectable as UNIXConnectable
-from ._core._sockets import as_connectable as as_connectable
-from ._core._sockets import connect_tcp as connect_tcp
-from ._core._sockets import connect_unix as connect_unix
-from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
-from ._core._sockets import (
- create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
-)
-from ._core._sockets import create_tcp_listener as create_tcp_listener
-from ._core._sockets import create_udp_socket as create_udp_socket
-from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
-from ._core._sockets import create_unix_listener as create_unix_listener
-from ._core._sockets import getaddrinfo as getaddrinfo
-from ._core._sockets import getnameinfo as getnameinfo
-from ._core._sockets import notify_closing as notify_closing
-from ._core._sockets import wait_readable as wait_readable
-from ._core._sockets import wait_socket_readable as wait_socket_readable
-from ._core._sockets import wait_socket_writable as wait_socket_writable
-from ._core._sockets import wait_writable as wait_writable
-from ._core._streams import create_memory_object_stream as create_memory_object_stream
-from ._core._subprocesses import open_process as open_process
-from ._core._subprocesses import run_process as run_process
-from ._core._synchronization import CapacityLimiter as CapacityLimiter
-from ._core._synchronization import (
- CapacityLimiterStatistics as CapacityLimiterStatistics,
-)
-from ._core._synchronization import Condition as Condition
-from ._core._synchronization import ConditionStatistics as ConditionStatistics
-from ._core._synchronization import Event as Event
-from ._core._synchronization import EventStatistics as EventStatistics
-from ._core._synchronization import Lock as Lock
-from ._core._synchronization import LockStatistics as LockStatistics
-from ._core._synchronization import ResourceGuard as ResourceGuard
-from ._core._synchronization import Semaphore as Semaphore
-from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
-from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
-from ._core._tasks import CancelScope as CancelScope
-from ._core._tasks import create_task_group as create_task_group
-from ._core._tasks import current_effective_deadline as current_effective_deadline
-from ._core._tasks import fail_after as fail_after
-from ._core._tasks import move_on_after as move_on_after
-from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
-from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
-from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
-from ._core._tempfile import TemporaryFile as TemporaryFile
-from ._core._tempfile import gettempdir as gettempdir
-from ._core._tempfile import gettempdirb as gettempdirb
-from ._core._tempfile import mkdtemp as mkdtemp
-from ._core._tempfile import mkstemp as mkstemp
-from ._core._testing import TaskInfo as TaskInfo
-from ._core._testing import get_current_task as get_current_task
-from ._core._testing import get_running_tasks as get_running_tasks
-from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
-from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
-from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
-from ._core._typedattr import typed_attribute as typed_attribute
-
-# Re-export imports so they look like they live directly in this package
-for __value in list(locals().values()):
- if getattr(__value, "__module__", "").startswith("anyio."):
- __value.__module__ = __name__
-
-
-del __value
-
-
-def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
- """Support deprecated aliases."""
- if attr == "BrokenWorkerIntepreter":
- import warnings
-
- warnings.warn(
- "The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return BrokenWorkerInterpreter
-
- raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 332281a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc
deleted file mode 100644
index 3918665..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/functools.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/functools.cpython-312.pyc
deleted file mode 100644
index 7950e5f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/functools.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc
deleted file mode 100644
index b4d7cde..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc
deleted file mode 100644
index f7798e7..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc
deleted file mode 100644
index 328a76b..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_process.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_process.cpython-312.pyc
deleted file mode 100644
index 8d7d869..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_process.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc
deleted file mode 100644
index 530c7d7..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__init__.py b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index f3af769..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc
deleted file mode 100644
index cf54ba1..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_trio.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_trio.cpython-312.pyc
deleted file mode 100644
index 0bc6a00..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_trio.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py
deleted file mode 100644
index 8ff009e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py
+++ /dev/null
@@ -1,2980 +0,0 @@
-from __future__ import annotations
-
-import array
-import asyncio
-import concurrent.futures
-import contextvars
-import math
-import os
-import socket
-import sys
-import threading
-import weakref
-from asyncio import (
- AbstractEventLoop,
- CancelledError,
- all_tasks,
- create_task,
- current_task,
- get_running_loop,
- sleep,
-)
-from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined]
-from collections import OrderedDict, deque
-from collections.abc import (
- AsyncGenerator,
- AsyncIterator,
- Awaitable,
- Callable,
- Collection,
- Coroutine,
- Iterable,
- Sequence,
-)
-from concurrent.futures import Future
-from contextlib import AbstractContextManager, suppress
-from contextvars import Context, copy_context
-from dataclasses import dataclass, field
-from functools import partial, wraps
-from inspect import (
- CORO_RUNNING,
- CORO_SUSPENDED,
- getcoroutinestate,
- iscoroutine,
-)
-from io import IOBase
-from os import PathLike
-from queue import Queue
-from signal import Signals
-from socket import AddressFamily, SocketKind
-from threading import Thread
-from types import CodeType, TracebackType
-from typing import (
- IO,
- TYPE_CHECKING,
- Any,
- Optional,
- TypeVar,
- cast,
-)
-from weakref import WeakKeyDictionary
-
-from .. import (
- CapacityLimiterStatistics,
- EventStatistics,
- LockStatistics,
- TaskInfo,
- abc,
-)
-from .._core._eventloop import (
- claim_worker_thread,
- set_current_async_library,
- threadlocals,
-)
-from .._core._exceptions import (
- BrokenResourceError,
- BusyResourceError,
- ClosedResourceError,
- EndOfStream,
- RunFinishedError,
- WouldBlock,
- iterate_exceptions,
-)
-from .._core._sockets import convert_ipv6_sockaddr
-from .._core._streams import create_memory_object_stream
-from .._core._synchronization import (
- CapacityLimiter as BaseCapacityLimiter,
-)
-from .._core._synchronization import Event as BaseEvent
-from .._core._synchronization import Lock as BaseLock
-from .._core._synchronization import (
- ResourceGuard,
- SemaphoreStatistics,
-)
-from .._core._synchronization import Semaphore as BaseSemaphore
-from .._core._tasks import CancelScope as BaseCancelScope
-from ..abc import (
- AsyncBackend,
- IPSockAddrType,
- SocketListener,
- UDPPacketType,
- UNIXDatagramPacketType,
-)
-from ..abc._eventloop import StrOrBytesPath
-from ..lowlevel import RunVar
-from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
-
-if TYPE_CHECKING:
- from _typeshed import FileDescriptorLike
-else:
- FileDescriptorLike = object
-
-if sys.version_info >= (3, 10):
- from typing import ParamSpec
-else:
- from typing_extensions import ParamSpec
-
-if sys.version_info >= (3, 11):
- from asyncio import Runner
- from typing import TypeVarTuple, Unpack
-else:
- import contextvars
- import enum
- import signal
- from asyncio import coroutines, events, exceptions, tasks
-
- from exceptiongroup import BaseExceptionGroup
- from typing_extensions import TypeVarTuple, Unpack
-
- class _State(enum.Enum):
- CREATED = "created"
- INITIALIZED = "initialized"
- CLOSED = "closed"
-
- class Runner:
- # Copied from CPython 3.11
- def __init__(
- self,
- *,
- debug: bool | None = None,
- loop_factory: Callable[[], AbstractEventLoop] | None = None,
- ):
- self._state = _State.CREATED
- self._debug = debug
- self._loop_factory = loop_factory
- self._loop: AbstractEventLoop | None = None
- self._context = None
- self._interrupt_count = 0
- self._set_event_loop = False
-
- def __enter__(self) -> Runner:
- self._lazy_init()
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.close()
-
- def close(self) -> None:
- """Shutdown and close event loop."""
- loop = self._loop
- if self._state is not _State.INITIALIZED or loop is None:
- return
- try:
- _cancel_all_tasks(loop)
- loop.run_until_complete(loop.shutdown_asyncgens())
- if hasattr(loop, "shutdown_default_executor"):
- loop.run_until_complete(loop.shutdown_default_executor())
- else:
- loop.run_until_complete(_shutdown_default_executor(loop))
- finally:
- if self._set_event_loop:
- events.set_event_loop(None)
- loop.close()
- self._loop = None
- self._state = _State.CLOSED
-
- def get_loop(self) -> AbstractEventLoop:
- """Return embedded event loop."""
- self._lazy_init()
- return self._loop
-
- def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval:
- """Run a coroutine inside the embedded event loop."""
- if not coroutines.iscoroutine(coro):
- raise ValueError(f"a coroutine was expected, got {coro!r}")
-
- if events._get_running_loop() is not None:
- # fail fast with short traceback
- raise RuntimeError(
- "Runner.run() cannot be called from a running event loop"
- )
-
- self._lazy_init()
-
- if context is None:
- context = self._context
- task = context.run(self._loop.create_task, coro)
-
- if (
- threading.current_thread() is threading.main_thread()
- and signal.getsignal(signal.SIGINT) is signal.default_int_handler
- ):
- sigint_handler = partial(self._on_sigint, main_task=task)
- try:
- signal.signal(signal.SIGINT, sigint_handler)
- except ValueError:
- # `signal.signal` may throw if `threading.main_thread` does
- # not support signals (e.g. embedded interpreter with signals
- # not registered - see gh-91880)
- sigint_handler = None
- else:
- sigint_handler = None
-
- self._interrupt_count = 0
- try:
- return self._loop.run_until_complete(task)
- except exceptions.CancelledError:
- if self._interrupt_count > 0:
- uncancel = getattr(task, "uncancel", None)
- if uncancel is not None and uncancel() == 0:
- raise KeyboardInterrupt # noqa: B904
- raise # CancelledError
- finally:
- if (
- sigint_handler is not None
- and signal.getsignal(signal.SIGINT) is sigint_handler
- ):
- signal.signal(signal.SIGINT, signal.default_int_handler)
-
- def _lazy_init(self) -> None:
- if self._state is _State.CLOSED:
- raise RuntimeError("Runner is closed")
- if self._state is _State.INITIALIZED:
- return
- if self._loop_factory is None:
- self._loop = events.new_event_loop()
- if not self._set_event_loop:
- # Call set_event_loop only once to avoid calling
- # attach_loop multiple times on child watchers
- events.set_event_loop(self._loop)
- self._set_event_loop = True
- else:
- self._loop = self._loop_factory()
- if self._debug is not None:
- self._loop.set_debug(self._debug)
- self._context = contextvars.copy_context()
- self._state = _State.INITIALIZED
-
- def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None:
- self._interrupt_count += 1
- if self._interrupt_count == 1 and not main_task.done():
- main_task.cancel()
- # wakeup loop if it is blocked by select() with long timeout
- self._loop.call_soon_threadsafe(lambda: None)
- return
- raise KeyboardInterrupt()
-
- def _cancel_all_tasks(loop: AbstractEventLoop) -> None:
- to_cancel = tasks.all_tasks(loop)
- if not to_cancel:
- return
-
- for task in to_cancel:
- task.cancel()
-
- loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
-
- for task in to_cancel:
- if task.cancelled():
- continue
- if task.exception() is not None:
- loop.call_exception_handler(
- {
- "message": "unhandled exception during asyncio.run() shutdown",
- "exception": task.exception(),
- "task": task,
- }
- )
-
- async def _shutdown_default_executor(loop: AbstractEventLoop) -> None:
- """Schedule the shutdown of the default executor."""
-
- def _do_shutdown(future: asyncio.futures.Future) -> None:
- try:
- loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined]
- loop.call_soon_threadsafe(future.set_result, None)
- except Exception as ex:
- loop.call_soon_threadsafe(future.set_exception, ex)
-
- loop._executor_shutdown_called = True
- if loop._default_executor is None:
- return
- future = loop.create_future()
- thread = threading.Thread(target=_do_shutdown, args=(future,))
- thread.start()
- try:
- await future
- finally:
- thread.join()
-
-
-T_Retval = TypeVar("T_Retval")
-T_contra = TypeVar("T_contra", contravariant=True)
-PosArgsT = TypeVarTuple("PosArgsT")
-P = ParamSpec("P")
-
-_root_task: RunVar[asyncio.Task | None] = RunVar("_root_task")
-
-
-def find_root_task() -> asyncio.Task:
- root_task = _root_task.get(None)
- if root_task is not None and not root_task.done():
- return root_task
-
- # Look for a task that has been started via run_until_complete()
- for task in all_tasks():
- if task._callbacks and not task.done():
- callbacks = [cb for cb, context in task._callbacks]
- for cb in callbacks:
- if (
- cb is _run_until_complete_cb
- or getattr(cb, "__module__", None) == "uvloop.loop"
- ):
- _root_task.set(task)
- return task
-
- # Look up the topmost task in the AnyIO task tree, if possible
- task = cast(asyncio.Task, current_task())
- state = _task_states.get(task)
- if state:
- cancel_scope = state.cancel_scope
- while cancel_scope and cancel_scope._parent_scope is not None:
- cancel_scope = cancel_scope._parent_scope
-
- if cancel_scope is not None:
- return cast(asyncio.Task, cancel_scope._host_task)
-
- return task
-
-
-def get_callable_name(func: Callable) -> str:
- module = getattr(func, "__module__", None)
- qualname = getattr(func, "__qualname__", None)
- return ".".join([x for x in (module, qualname) if x])
-
-
-#
-# Event loop
-#
-
-_run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary()
-
-
-def _task_started(task: asyncio.Task) -> bool:
- """Return ``True`` if the task has been started and has not finished."""
- # The task coro should never be None here, as we never add finished tasks to the
- # task list
- coro = task.get_coro()
- assert coro is not None
- try:
- return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED)
- except AttributeError:
- # task coro is async_genenerator_asend https://bugs.python.org/issue37771
- raise Exception(f"Cannot determine if task {task} has started or not") from None
-
-
-#
-# Timeouts and cancellation
-#
-
-
-def is_anyio_cancellation(exc: CancelledError) -> bool:
- # Sometimes third party frameworks catch a CancelledError and raise a new one, so as
- # a workaround we have to look at the previous ones in __context__ too for a
- # matching cancel message
- while True:
- if (
- exc.args
- and isinstance(exc.args[0], str)
- and exc.args[0].startswith("Cancelled via cancel scope ")
- ):
- return True
-
- if isinstance(exc.__context__, CancelledError):
- exc = exc.__context__
- continue
-
- return False
-
-
-class CancelScope(BaseCancelScope):
- def __new__(
- cls, *, deadline: float = math.inf, shield: bool = False
- ) -> CancelScope:
- return object.__new__(cls)
-
- def __init__(self, deadline: float = math.inf, shield: bool = False):
- self._deadline = deadline
- self._shield = shield
- self._parent_scope: CancelScope | None = None
- self._child_scopes: set[CancelScope] = set()
- self._cancel_called = False
- self._cancel_reason: str | None = None
- self._cancelled_caught = False
- self._active = False
- self._timeout_handle: asyncio.TimerHandle | None = None
- self._cancel_handle: asyncio.Handle | None = None
- self._tasks: set[asyncio.Task] = set()
- self._host_task: asyncio.Task | None = None
- if sys.version_info >= (3, 11):
- self._pending_uncancellations: int | None = 0
- else:
- self._pending_uncancellations = None
-
- def __enter__(self) -> CancelScope:
- if self._active:
- raise RuntimeError(
- "Each CancelScope may only be used for a single 'with' block"
- )
-
- self._host_task = host_task = cast(asyncio.Task, current_task())
- self._tasks.add(host_task)
- try:
- task_state = _task_states[host_task]
- except KeyError:
- task_state = TaskState(None, self)
- _task_states[host_task] = task_state
- else:
- self._parent_scope = task_state.cancel_scope
- task_state.cancel_scope = self
- if self._parent_scope is not None:
- # If using an eager task factory, the parent scope may not even contain
- # the host task
- self._parent_scope._child_scopes.add(self)
- self._parent_scope._tasks.discard(host_task)
-
- self._timeout()
- self._active = True
-
- # Start cancelling the host task if the scope was cancelled before entering
- if self._cancel_called:
- self._deliver_cancellation(self)
-
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- del exc_tb
-
- if not self._active:
- raise RuntimeError("This cancel scope is not active")
- if current_task() is not self._host_task:
- raise RuntimeError(
- "Attempted to exit cancel scope in a different task than it was "
- "entered in"
- )
-
- assert self._host_task is not None
- host_task_state = _task_states.get(self._host_task)
- if host_task_state is None or host_task_state.cancel_scope is not self:
- raise RuntimeError(
- "Attempted to exit a cancel scope that isn't the current tasks's "
- "current cancel scope"
- )
-
- try:
- self._active = False
- if self._timeout_handle:
- self._timeout_handle.cancel()
- self._timeout_handle = None
-
- self._tasks.remove(self._host_task)
- if self._parent_scope is not None:
- self._parent_scope._child_scopes.remove(self)
- self._parent_scope._tasks.add(self._host_task)
-
- host_task_state.cancel_scope = self._parent_scope
-
- # Restart the cancellation effort in the closest visible, cancelled parent
- # scope if necessary
- self._restart_cancellation_in_parent()
-
- # We only swallow the exception iff it was an AnyIO CancelledError, either
- # directly as exc_val or inside an exception group and there are no cancelled
- # parent cancel scopes visible to us here
- if self._cancel_called and not self._parent_cancellation_is_visible_to_us:
- # For each level-cancel() call made on the host task, call uncancel()
- while self._pending_uncancellations:
- self._host_task.uncancel()
- self._pending_uncancellations -= 1
-
- # Update cancelled_caught and check for exceptions we must not swallow
- cannot_swallow_exc_val = False
- if exc_val is not None:
- for exc in iterate_exceptions(exc_val):
- if isinstance(exc, CancelledError) and is_anyio_cancellation(
- exc
- ):
- self._cancelled_caught = True
- else:
- cannot_swallow_exc_val = True
-
- return self._cancelled_caught and not cannot_swallow_exc_val
- else:
- if self._pending_uncancellations:
- assert self._parent_scope is not None
- assert self._parent_scope._pending_uncancellations is not None
- self._parent_scope._pending_uncancellations += (
- self._pending_uncancellations
- )
- self._pending_uncancellations = 0
-
- return False
- finally:
- self._host_task = None
- del exc_val
-
- @property
- def _effectively_cancelled(self) -> bool:
- cancel_scope: CancelScope | None = self
- while cancel_scope is not None:
- if cancel_scope._cancel_called:
- return True
-
- if cancel_scope.shield:
- return False
-
- cancel_scope = cancel_scope._parent_scope
-
- return False
-
- @property
- def _parent_cancellation_is_visible_to_us(self) -> bool:
- return (
- self._parent_scope is not None
- and not self.shield
- and self._parent_scope._effectively_cancelled
- )
-
- def _timeout(self) -> None:
- if self._deadline != math.inf:
- loop = get_running_loop()
- if loop.time() >= self._deadline:
- self.cancel("deadline exceeded")
- else:
- self._timeout_handle = loop.call_at(self._deadline, self._timeout)
-
- def _deliver_cancellation(self, origin: CancelScope) -> bool:
- """
- Deliver cancellation to directly contained tasks and nested cancel scopes.
-
- Schedule another run at the end if we still have tasks eligible for
- cancellation.
-
- :param origin: the cancel scope that originated the cancellation
- :return: ``True`` if the delivery needs to be retried on the next cycle
-
- """
- should_retry = False
- current = current_task()
- for task in self._tasks:
- should_retry = True
- if task._must_cancel: # type: ignore[attr-defined]
- continue
-
- # The task is eligible for cancellation if it has started
- if task is not current and (task is self._host_task or _task_started(task)):
- waiter = task._fut_waiter # type: ignore[attr-defined]
- if not isinstance(waiter, asyncio.Future) or not waiter.done():
- task.cancel(origin._cancel_reason)
- if (
- task is origin._host_task
- and origin._pending_uncancellations is not None
- ):
- origin._pending_uncancellations += 1
-
- # Deliver cancellation to child scopes that aren't shielded or running their own
- # cancellation callbacks
- for scope in self._child_scopes:
- if not scope._shield and not scope.cancel_called:
- should_retry = scope._deliver_cancellation(origin) or should_retry
-
- # Schedule another callback if there are still tasks left
- if origin is self:
- if should_retry:
- self._cancel_handle = get_running_loop().call_soon(
- self._deliver_cancellation, origin
- )
- else:
- self._cancel_handle = None
-
- return should_retry
-
- def _restart_cancellation_in_parent(self) -> None:
- """
- Restart the cancellation effort in the closest directly cancelled parent scope.
-
- """
- scope = self._parent_scope
- while scope is not None:
- if scope._cancel_called:
- if scope._cancel_handle is None:
- scope._deliver_cancellation(scope)
-
- break
-
- # No point in looking beyond any shielded scope
- if scope._shield:
- break
-
- scope = scope._parent_scope
-
- def cancel(self, reason: str | None = None) -> None:
- if not self._cancel_called:
- if self._timeout_handle:
- self._timeout_handle.cancel()
- self._timeout_handle = None
-
- self._cancel_called = True
- self._cancel_reason = f"Cancelled via cancel scope {id(self):x}"
- if task := current_task():
- self._cancel_reason += f" by {task}"
-
- if reason:
- self._cancel_reason += f"; reason: {reason}"
-
- if self._host_task is not None:
- self._deliver_cancellation(self)
-
- @property
- def deadline(self) -> float:
- return self._deadline
-
- @deadline.setter
- def deadline(self, value: float) -> None:
- self._deadline = float(value)
- if self._timeout_handle is not None:
- self._timeout_handle.cancel()
- self._timeout_handle = None
-
- if self._active and not self._cancel_called:
- self._timeout()
-
- @property
- def cancel_called(self) -> bool:
- return self._cancel_called
-
- @property
- def cancelled_caught(self) -> bool:
- return self._cancelled_caught
-
- @property
- def shield(self) -> bool:
- return self._shield
-
- @shield.setter
- def shield(self, value: bool) -> None:
- if self._shield != value:
- self._shield = value
- if not value:
- self._restart_cancellation_in_parent()
-
-
-#
-# Task states
-#
-
-
-class TaskState:
- """
- Encapsulates auxiliary task information that cannot be added to the Task instance
- itself because there are no guarantees about its implementation.
- """
-
- __slots__ = "parent_id", "cancel_scope", "__weakref__"
-
- def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None):
- self.parent_id = parent_id
- self.cancel_scope = cancel_scope
-
-
-_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary()
-
-
-#
-# Task groups
-#
-
-
-class _AsyncioTaskStatus(abc.TaskStatus):
- def __init__(self, future: asyncio.Future, parent_id: int):
- self._future = future
- self._parent_id = parent_id
-
- def started(self, value: T_contra | None = None) -> None:
- try:
- self._future.set_result(value)
- except asyncio.InvalidStateError:
- if not self._future.cancelled():
- raise RuntimeError(
- "called 'started' twice on the same task status"
- ) from None
-
- task = cast(asyncio.Task, current_task())
- _task_states[task].parent_id = self._parent_id
-
-
-if sys.version_info >= (3, 12):
- _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__
-else:
- _eager_task_factory_code = None
-
-
-class TaskGroup(abc.TaskGroup):
- def __init__(self) -> None:
- self.cancel_scope: CancelScope = CancelScope()
- self._active = False
- self._exceptions: list[BaseException] = []
- self._tasks: set[asyncio.Task] = set()
- self._on_completed_fut: asyncio.Future[None] | None = None
-
- async def __aenter__(self) -> TaskGroup:
- self.cancel_scope.__enter__()
- self._active = True
- return self
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- try:
- if exc_val is not None:
- self.cancel_scope.cancel()
- if not isinstance(exc_val, CancelledError):
- self._exceptions.append(exc_val)
-
- loop = get_running_loop()
- try:
- if self._tasks:
- with CancelScope() as wait_scope:
- while self._tasks:
- self._on_completed_fut = loop.create_future()
-
- try:
- await self._on_completed_fut
- except CancelledError as exc:
- # Shield the scope against further cancellation attempts,
- # as they're not productive (#695)
- wait_scope.shield = True
- self.cancel_scope.cancel()
-
- # Set exc_val from the cancellation exception if it was
- # previously unset. However, we should not replace a native
- # cancellation exception with one raise by a cancel scope.
- if exc_val is None or (
- isinstance(exc_val, CancelledError)
- and not is_anyio_cancellation(exc)
- ):
- exc_val = exc
-
- self._on_completed_fut = None
- else:
- # If there are no child tasks to wait on, run at least one checkpoint
- # anyway
- await AsyncIOBackend.cancel_shielded_checkpoint()
-
- self._active = False
- if self._exceptions:
- # The exception that got us here should already have been
- # added to self._exceptions so it's ok to break exception
- # chaining and avoid adding a "During handling of above..."
- # for each nesting level.
- raise BaseExceptionGroup(
- "unhandled errors in a TaskGroup", self._exceptions
- ) from None
- elif exc_val:
- raise exc_val
- except BaseException as exc:
- if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
- return True
-
- raise
-
- return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb)
- finally:
- del exc_val, exc_tb, self._exceptions
-
- def _spawn(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[Any]],
- args: tuple[Unpack[PosArgsT]],
- name: object,
- task_status_future: asyncio.Future | None = None,
- ) -> asyncio.Task:
- def task_done(_task: asyncio.Task) -> None:
- if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
- asyncio.future_discard_from_awaited_by(
- _task, self.cancel_scope._host_task
- )
-
- task_state = _task_states[_task]
- assert task_state.cancel_scope is not None
- assert _task in task_state.cancel_scope._tasks
- task_state.cancel_scope._tasks.remove(_task)
- self._tasks.remove(task)
- del _task_states[_task]
-
- if self._on_completed_fut is not None and not self._tasks:
- try:
- self._on_completed_fut.set_result(None)
- except asyncio.InvalidStateError:
- pass
-
- try:
- exc = _task.exception()
- except CancelledError as e:
- while isinstance(e.__context__, CancelledError):
- e = e.__context__
-
- exc = e
-
- if exc is not None:
- # The future can only be in the cancelled state if the host task was
- # cancelled, so return immediately instead of adding one more
- # CancelledError to the exceptions list
- if task_status_future is not None and task_status_future.cancelled():
- return
-
- if task_status_future is None or task_status_future.done():
- if not isinstance(exc, CancelledError):
- self._exceptions.append(exc)
-
- if not self.cancel_scope._effectively_cancelled:
- self.cancel_scope.cancel()
- else:
- task_status_future.set_exception(exc)
- elif task_status_future is not None and not task_status_future.done():
- task_status_future.set_exception(
- RuntimeError("Child exited without calling task_status.started()")
- )
-
- if not self._active:
- raise RuntimeError(
- "This task group is not active; no new tasks can be started."
- )
-
- kwargs = {}
- if task_status_future:
- parent_id = id(current_task())
- kwargs["task_status"] = _AsyncioTaskStatus(
- task_status_future, id(self.cancel_scope._host_task)
- )
- else:
- parent_id = id(self.cancel_scope._host_task)
-
- coro = func(*args, **kwargs)
- if not iscoroutine(coro):
- prefix = f"{func.__module__}." if hasattr(func, "__module__") else ""
- raise TypeError(
- f"Expected {prefix}{func.__qualname__}() to return a coroutine, but "
- f"the return value ({coro!r}) is not a coroutine object"
- )
-
- name = get_callable_name(func) if name is None else str(name)
- loop = asyncio.get_running_loop()
- if (
- (factory := loop.get_task_factory())
- and getattr(factory, "__code__", None) is _eager_task_factory_code
- and (closure := getattr(factory, "__closure__", None))
- ):
- custom_task_constructor = closure[0].cell_contents
- task = custom_task_constructor(coro, loop=loop, name=name)
- else:
- task = create_task(coro, name=name)
-
- # Make the spawned task inherit the task group's cancel scope
- _task_states[task] = TaskState(
- parent_id=parent_id, cancel_scope=self.cancel_scope
- )
- self.cancel_scope._tasks.add(task)
- self._tasks.add(task)
- if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
- asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task)
-
- task.add_done_callback(task_done)
- return task
-
- def start_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[Any]],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> None:
- self._spawn(func, args, name)
-
- async def start(
- self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None
- ) -> Any:
- future: asyncio.Future = asyncio.Future()
- task = self._spawn(func, args, name, future)
-
- # If the task raises an exception after sending a start value without a switch
- # point between, the task group is cancelled and this method never proceeds to
- # process the completed future. That's why we have to have a shielded cancel
- # scope here.
- try:
- return await future
- except CancelledError:
- # Cancel the task and wait for it to exit before returning
- task.cancel()
- with CancelScope(shield=True), suppress(CancelledError):
- await task
-
- raise
-
-
-#
-# Threads
-#
-
-_Retval_Queue_Type = tuple[Optional[T_Retval], Optional[BaseException]]
-
-
-class WorkerThread(Thread):
- MAX_IDLE_TIME = 10 # seconds
-
- def __init__(
- self,
- root_task: asyncio.Task,
- workers: set[WorkerThread],
- idle_workers: deque[WorkerThread],
- ):
- super().__init__(name="AnyIO worker thread")
- self.root_task = root_task
- self.workers = workers
- self.idle_workers = idle_workers
- self.loop = root_task._loop
- self.queue: Queue[
- tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None
- ] = Queue(2)
- self.idle_since = AsyncIOBackend.current_time()
- self.stopping = False
-
- def _report_result(
- self, future: asyncio.Future, result: Any, exc: BaseException | None
- ) -> None:
- self.idle_since = AsyncIOBackend.current_time()
- if not self.stopping:
- self.idle_workers.append(self)
-
- if not future.cancelled():
- if exc is not None:
- if isinstance(exc, StopIteration):
- new_exc = RuntimeError("coroutine raised StopIteration")
- new_exc.__cause__ = exc
- exc = new_exc
-
- future.set_exception(exc)
- else:
- future.set_result(result)
-
- def run(self) -> None:
- with claim_worker_thread(AsyncIOBackend, self.loop):
- while True:
- item = self.queue.get()
- if item is None:
- # Shutdown command received
- return
-
- context, func, args, future, cancel_scope = item
- if not future.cancelled():
- result = None
- exception: BaseException | None = None
- threadlocals.current_cancel_scope = cancel_scope
- try:
- result = context.run(func, *args)
- except BaseException as exc:
- exception = exc
- finally:
- del threadlocals.current_cancel_scope
-
- if not self.loop.is_closed():
- self.loop.call_soon_threadsafe(
- self._report_result, future, result, exception
- )
-
- del result, exception
-
- self.queue.task_done()
- del item, context, func, args, future, cancel_scope
-
- def stop(self, f: asyncio.Task | None = None) -> None:
- self.stopping = True
- self.queue.put_nowait(None)
- self.workers.discard(self)
- try:
- self.idle_workers.remove(self)
- except ValueError:
- pass
-
-
-_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar(
- "_threadpool_idle_workers"
-)
-_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers")
-
-
-#
-# Subprocesses
-#
-
-
-@dataclass(eq=False)
-class StreamReaderWrapper(abc.ByteReceiveStream):
- _stream: asyncio.StreamReader
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- data = await self._stream.read(max_bytes)
- if data:
- return data
- else:
- raise EndOfStream
-
- async def aclose(self) -> None:
- self._stream.set_exception(ClosedResourceError())
- await AsyncIOBackend.checkpoint()
-
-
-@dataclass(eq=False)
-class StreamWriterWrapper(abc.ByteSendStream):
- _stream: asyncio.StreamWriter
- _closed: bool = field(init=False, default=False)
-
- async def send(self, item: bytes) -> None:
- await AsyncIOBackend.checkpoint_if_cancelled()
- stream_paused = self._stream._protocol._paused # type: ignore[attr-defined]
- try:
- self._stream.write(item)
- await self._stream.drain()
- except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc:
- # If closed by us and/or the peer:
- # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError
- # * on uvloop and Winloop, write() eventually starts raising RuntimeError
- if self._closed:
- raise ClosedResourceError from exc
- elif self._stream.is_closing():
- raise BrokenResourceError from exc
-
- raise
-
- if not stream_paused:
- await AsyncIOBackend.cancel_shielded_checkpoint()
-
- async def aclose(self) -> None:
- self._closed = True
- self._stream.close()
- await AsyncIOBackend.checkpoint()
-
-
-@dataclass(eq=False)
-class Process(abc.Process):
- _process: asyncio.subprocess.Process
- _stdin: StreamWriterWrapper | None
- _stdout: StreamReaderWrapper | None
- _stderr: StreamReaderWrapper | None
-
- async def aclose(self) -> None:
- with CancelScope(shield=True) as scope:
- if self._stdin:
- await self._stdin.aclose()
- if self._stdout:
- await self._stdout.aclose()
- if self._stderr:
- await self._stderr.aclose()
-
- scope.shield = False
- try:
- await self.wait()
- except BaseException:
- scope.shield = True
- self.kill()
- await self.wait()
- raise
-
- async def wait(self) -> int:
- return await self._process.wait()
-
- def terminate(self) -> None:
- self._process.terminate()
-
- def kill(self) -> None:
- self._process.kill()
-
- def send_signal(self, signal: int) -> None:
- self._process.send_signal(signal)
-
- @property
- def pid(self) -> int:
- return self._process.pid
-
- @property
- def returncode(self) -> int | None:
- return self._process.returncode
-
- @property
- def stdin(self) -> abc.ByteSendStream | None:
- return self._stdin
-
- @property
- def stdout(self) -> abc.ByteReceiveStream | None:
- return self._stdout
-
- @property
- def stderr(self) -> abc.ByteReceiveStream | None:
- return self._stderr
-
-
-def _forcibly_shutdown_process_pool_on_exit(
- workers: set[Process], _task: object
-) -> None:
- """
- Forcibly shuts down worker processes belonging to this event loop."""
- child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined]
- if sys.version_info < (3, 12):
- try:
- child_watcher = asyncio.get_event_loop_policy().get_child_watcher()
- except NotImplementedError:
- pass
-
- # Close as much as possible (w/o async/await) to avoid warnings
- for process in workers.copy():
- if process.returncode is None:
- continue
-
- process._stdin._stream._transport.close() # type: ignore[union-attr]
- process._stdout._stream._transport.close() # type: ignore[union-attr]
- process._stderr._stream._transport.close() # type: ignore[union-attr]
- process.kill()
- if child_watcher:
- child_watcher.remove_child_handler(process.pid)
-
-
-async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None:
- """
- Shuts down worker processes belonging to this event loop.
-
- NOTE: this only works when the event loop was started using asyncio.run() or
- anyio.run().
-
- """
- process: abc.Process
- try:
- await sleep(math.inf)
- except asyncio.CancelledError:
- workers = workers.copy()
- for process in workers:
- if process.returncode is None:
- process.kill()
-
- for process in workers:
- await process.aclose()
-
-
-#
-# Sockets and networking
-#
-
-
-class StreamProtocol(asyncio.Protocol):
- read_queue: deque[bytes]
- read_event: asyncio.Event
- write_event: asyncio.Event
- exception: Exception | None = None
- is_at_eof: bool = False
-
- def connection_made(self, transport: asyncio.BaseTransport) -> None:
- self.read_queue = deque()
- self.read_event = asyncio.Event()
- self.write_event = asyncio.Event()
- self.write_event.set()
- cast(asyncio.Transport, transport).set_write_buffer_limits(0)
-
- def connection_lost(self, exc: Exception | None) -> None:
- if exc:
- self.exception = BrokenResourceError()
- self.exception.__cause__ = exc
-
- self.read_event.set()
- self.write_event.set()
-
- def data_received(self, data: bytes) -> None:
- # ProactorEventloop sometimes sends bytearray instead of bytes
- self.read_queue.append(bytes(data))
- self.read_event.set()
-
- def eof_received(self) -> bool | None:
- self.is_at_eof = True
- self.read_event.set()
- return True
-
- def pause_writing(self) -> None:
- self.write_event = asyncio.Event()
-
- def resume_writing(self) -> None:
- self.write_event.set()
-
-
-class DatagramProtocol(asyncio.DatagramProtocol):
- read_queue: deque[tuple[bytes, IPSockAddrType]]
- read_event: asyncio.Event
- write_event: asyncio.Event
- exception: Exception | None = None
-
- def connection_made(self, transport: asyncio.BaseTransport) -> None:
- self.read_queue = deque(maxlen=100) # arbitrary value
- self.read_event = asyncio.Event()
- self.write_event = asyncio.Event()
- self.write_event.set()
-
- def connection_lost(self, exc: Exception | None) -> None:
- self.read_event.set()
- self.write_event.set()
-
- def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None:
- addr = convert_ipv6_sockaddr(addr)
- self.read_queue.append((data, addr))
- self.read_event.set()
-
- def error_received(self, exc: Exception) -> None:
- self.exception = exc
-
- def pause_writing(self) -> None:
- self.write_event.clear()
-
- def resume_writing(self) -> None:
- self.write_event.set()
-
-
-class SocketStream(abc.SocketStream):
- def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol):
- self._transport = transport
- self._protocol = protocol
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
- self._closed = False
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self._transport.get_extra_info("socket")
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- with self._receive_guard:
- if (
- not self._protocol.read_event.is_set()
- and not self._transport.is_closing()
- and not self._protocol.is_at_eof
- ):
- self._transport.resume_reading()
- await self._protocol.read_event.wait()
- self._transport.pause_reading()
- else:
- await AsyncIOBackend.checkpoint()
-
- try:
- chunk = self._protocol.read_queue.popleft()
- except IndexError:
- if self._closed:
- raise ClosedResourceError from None
- elif self._protocol.exception:
- raise self._protocol.exception from None
- else:
- raise EndOfStream from None
-
- if len(chunk) > max_bytes:
- # Split the oversized chunk
- chunk, leftover = chunk[:max_bytes], chunk[max_bytes:]
- self._protocol.read_queue.appendleft(leftover)
-
- # If the read queue is empty, clear the flag so that the next call will
- # block until data is available
- if not self._protocol.read_queue:
- self._protocol.read_event.clear()
-
- return chunk
-
- async def send(self, item: bytes) -> None:
- with self._send_guard:
- await AsyncIOBackend.checkpoint()
-
- if self._closed:
- raise ClosedResourceError
- elif self._protocol.exception is not None:
- raise self._protocol.exception
-
- try:
- self._transport.write(item)
- except RuntimeError as exc:
- if self._transport.is_closing():
- raise BrokenResourceError from exc
- else:
- raise
-
- await self._protocol.write_event.wait()
-
- async def send_eof(self) -> None:
- try:
- self._transport.write_eof()
- except OSError:
- pass
-
- async def aclose(self) -> None:
- self._closed = True
- if not self._transport.is_closing():
- try:
- self._transport.write_eof()
- except OSError:
- pass
-
- self._transport.close()
- await sleep(0)
- self._transport.abort()
-
-
-class _RawSocketMixin:
- _receive_future: asyncio.Future | None = None
- _send_future: asyncio.Future | None = None
- _closing = False
-
- def __init__(self, raw_socket: socket.socket):
- self.__raw_socket = raw_socket
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self.__raw_socket
-
- def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
- def callback(f: object) -> None:
- del self._receive_future
- loop.remove_reader(self.__raw_socket)
-
- f = self._receive_future = asyncio.Future()
- loop.add_reader(self.__raw_socket, f.set_result, None)
- f.add_done_callback(callback)
- return f
-
- def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
- def callback(f: object) -> None:
- del self._send_future
- loop.remove_writer(self.__raw_socket)
-
- f = self._send_future = asyncio.Future()
- loop.add_writer(self.__raw_socket, f.set_result, None)
- f.add_done_callback(callback)
- return f
-
- async def aclose(self) -> None:
- if not self._closing:
- self._closing = True
- if self.__raw_socket.fileno() != -1:
- self.__raw_socket.close()
-
- if self._receive_future:
- self._receive_future.set_result(None)
- if self._send_future:
- self._send_future.set_result(None)
-
-
-class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream):
- async def send_eof(self) -> None:
- with self._send_guard:
- self._raw_socket.shutdown(socket.SHUT_WR)
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._receive_guard:
- while True:
- try:
- data = self._raw_socket.recv(max_bytes)
- except BlockingIOError:
- await self._wait_until_readable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- if not data:
- raise EndOfStream
-
- return data
-
- async def send(self, item: bytes) -> None:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._send_guard:
- view = memoryview(item)
- while view:
- try:
- bytes_sent = self._raw_socket.send(view)
- except BlockingIOError:
- await self._wait_until_writable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- view = view[bytes_sent:]
-
- async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
- if not isinstance(msglen, int) or msglen < 0:
- raise ValueError("msglen must be a non-negative integer")
- if not isinstance(maxfds, int) or maxfds < 1:
- raise ValueError("maxfds must be a positive integer")
-
- loop = get_running_loop()
- fds = array.array("i")
- await AsyncIOBackend.checkpoint()
- with self._receive_guard:
- while True:
- try:
- message, ancdata, flags, addr = self._raw_socket.recvmsg(
- msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
- )
- except BlockingIOError:
- await self._wait_until_readable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- if not message and not ancdata:
- raise EndOfStream
-
- break
-
- for cmsg_level, cmsg_type, cmsg_data in ancdata:
- if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
- raise RuntimeError(
- f"Received unexpected ancillary data; message = {message!r}, "
- f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
- )
-
- fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
-
- return message, list(fds)
-
- async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
- if not message:
- raise ValueError("message must not be empty")
- if not fds:
- raise ValueError("fds must not be empty")
-
- loop = get_running_loop()
- filenos: list[int] = []
- for fd in fds:
- if isinstance(fd, int):
- filenos.append(fd)
- elif isinstance(fd, IOBase):
- filenos.append(fd.fileno())
-
- fdarray = array.array("i", filenos)
- await AsyncIOBackend.checkpoint()
- with self._send_guard:
- while True:
- try:
- # The ignore can be removed after mypy picks up
- # https://github.com/python/typeshed/pull/5545
- self._raw_socket.sendmsg(
- [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)]
- )
- break
- except BlockingIOError:
- await self._wait_until_writable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
-
-
-class TCPSocketListener(abc.SocketListener):
- _accept_scope: CancelScope | None = None
- _closed = False
-
- def __init__(self, raw_socket: socket.socket):
- self.__raw_socket = raw_socket
- self._loop = cast(asyncio.BaseEventLoop, get_running_loop())
- self._accept_guard = ResourceGuard("accepting connections from")
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self.__raw_socket
-
- async def accept(self) -> abc.SocketStream:
- if self._closed:
- raise ClosedResourceError
-
- with self._accept_guard:
- await AsyncIOBackend.checkpoint()
- with CancelScope() as self._accept_scope:
- try:
- client_sock, _addr = await self._loop.sock_accept(self._raw_socket)
- except asyncio.CancelledError:
- # Workaround for https://bugs.python.org/issue41317
- try:
- self._loop.remove_reader(self._raw_socket)
- except (ValueError, NotImplementedError):
- pass
-
- if self._closed:
- raise ClosedResourceError from None
-
- raise
- finally:
- self._accept_scope = None
-
- client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- transport, protocol = await self._loop.connect_accepted_socket(
- StreamProtocol, client_sock
- )
- return SocketStream(transport, protocol)
-
- async def aclose(self) -> None:
- if self._closed:
- return
-
- self._closed = True
- if self._accept_scope:
- # Workaround for https://bugs.python.org/issue41317
- try:
- self._loop.remove_reader(self._raw_socket)
- except (ValueError, NotImplementedError):
- pass
-
- self._accept_scope.cancel()
- await sleep(0)
-
- self._raw_socket.close()
-
-
-class UNIXSocketListener(abc.SocketListener):
- def __init__(self, raw_socket: socket.socket):
- self.__raw_socket = raw_socket
- self._loop = get_running_loop()
- self._accept_guard = ResourceGuard("accepting connections from")
- self._closed = False
-
- async def accept(self) -> abc.SocketStream:
- await AsyncIOBackend.checkpoint()
- with self._accept_guard:
- while True:
- try:
- client_sock, _ = self.__raw_socket.accept()
- client_sock.setblocking(False)
- return UNIXSocketStream(client_sock)
- except BlockingIOError:
- f: asyncio.Future = asyncio.Future()
- self._loop.add_reader(self.__raw_socket, f.set_result, None)
- f.add_done_callback(
- lambda _: self._loop.remove_reader(self.__raw_socket)
- )
- await f
- except OSError as exc:
- if self._closed:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
-
- async def aclose(self) -> None:
- self._closed = True
- self.__raw_socket.close()
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self.__raw_socket
-
-
-class UDPSocket(abc.UDPSocket):
- def __init__(
- self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
- ):
- self._transport = transport
- self._protocol = protocol
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
- self._closed = False
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self._transport.get_extra_info("socket")
-
- async def aclose(self) -> None:
- self._closed = True
- if not self._transport.is_closing():
- self._transport.close()
-
- async def receive(self) -> tuple[bytes, IPSockAddrType]:
- with self._receive_guard:
- await AsyncIOBackend.checkpoint()
-
- # If the buffer is empty, ask for more data
- if not self._protocol.read_queue and not self._transport.is_closing():
- self._protocol.read_event.clear()
- await self._protocol.read_event.wait()
-
- try:
- return self._protocol.read_queue.popleft()
- except IndexError:
- if self._closed:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from None
-
- async def send(self, item: UDPPacketType) -> None:
- with self._send_guard:
- await AsyncIOBackend.checkpoint()
- await self._protocol.write_event.wait()
- if self._closed:
- raise ClosedResourceError
- elif self._transport.is_closing():
- raise BrokenResourceError
- else:
- self._transport.sendto(*item)
-
-
-class ConnectedUDPSocket(abc.ConnectedUDPSocket):
- def __init__(
- self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
- ):
- self._transport = transport
- self._protocol = protocol
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
- self._closed = False
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self._transport.get_extra_info("socket")
-
- async def aclose(self) -> None:
- self._closed = True
- if not self._transport.is_closing():
- self._transport.close()
-
- async def receive(self) -> bytes:
- with self._receive_guard:
- await AsyncIOBackend.checkpoint()
-
- # If the buffer is empty, ask for more data
- if not self._protocol.read_queue and not self._transport.is_closing():
- self._protocol.read_event.clear()
- await self._protocol.read_event.wait()
-
- try:
- packet = self._protocol.read_queue.popleft()
- except IndexError:
- if self._closed:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from None
-
- return packet[0]
-
- async def send(self, item: bytes) -> None:
- with self._send_guard:
- await AsyncIOBackend.checkpoint()
- await self._protocol.write_event.wait()
- if self._closed:
- raise ClosedResourceError
- elif self._transport.is_closing():
- raise BrokenResourceError
- else:
- self._transport.sendto(item)
-
-
-class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket):
- async def receive(self) -> UNIXDatagramPacketType:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._receive_guard:
- while True:
- try:
- data = self._raw_socket.recvfrom(65536)
- except BlockingIOError:
- await self._wait_until_readable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- return data
-
- async def send(self, item: UNIXDatagramPacketType) -> None:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._send_guard:
- while True:
- try:
- self._raw_socket.sendto(*item)
- except BlockingIOError:
- await self._wait_until_writable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- return
-
-
-class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket):
- async def receive(self) -> bytes:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._receive_guard:
- while True:
- try:
- data = self._raw_socket.recv(65536)
- except BlockingIOError:
- await self._wait_until_readable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- return data
-
- async def send(self, item: bytes) -> None:
- loop = get_running_loop()
- await AsyncIOBackend.checkpoint()
- with self._send_guard:
- while True:
- try:
- self._raw_socket.send(item)
- except BlockingIOError:
- await self._wait_until_writable(loop)
- except OSError as exc:
- if self._closing:
- raise ClosedResourceError from None
- else:
- raise BrokenResourceError from exc
- else:
- return
-
-
-_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events")
-_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events")
-
-
-#
-# Synchronization
-#
-
-
-class Event(BaseEvent):
- def __new__(cls) -> Event:
- return object.__new__(cls)
-
- def __init__(self) -> None:
- self._event = asyncio.Event()
-
- def set(self) -> None:
- self._event.set()
-
- def is_set(self) -> bool:
- return self._event.is_set()
-
- async def wait(self) -> None:
- if self.is_set():
- await AsyncIOBackend.checkpoint()
- else:
- await self._event.wait()
-
- def statistics(self) -> EventStatistics:
- return EventStatistics(len(self._event._waiters))
-
-
-class Lock(BaseLock):
- def __new__(cls, *, fast_acquire: bool = False) -> Lock:
- return object.__new__(cls)
-
- def __init__(self, *, fast_acquire: bool = False) -> None:
- self._fast_acquire = fast_acquire
- self._owner_task: asyncio.Task | None = None
- self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque()
-
- async def acquire(self) -> None:
- task = cast(asyncio.Task, current_task())
- if self._owner_task is None and not self._waiters:
- await AsyncIOBackend.checkpoint_if_cancelled()
- self._owner_task = task
-
- # Unless on the "fast path", yield control of the event loop so that other
- # tasks can run too
- if not self._fast_acquire:
- try:
- await AsyncIOBackend.cancel_shielded_checkpoint()
- except CancelledError:
- self.release()
- raise
-
- return
-
- if self._owner_task == task:
- raise RuntimeError("Attempted to acquire an already held Lock")
-
- fut: asyncio.Future[None] = asyncio.Future()
- item = task, fut
- self._waiters.append(item)
- try:
- await fut
- except CancelledError:
- self._waiters.remove(item)
- if self._owner_task is task:
- self.release()
-
- raise
-
- self._waiters.remove(item)
-
- def acquire_nowait(self) -> None:
- task = cast(asyncio.Task, current_task())
- if self._owner_task is None and not self._waiters:
- self._owner_task = task
- return
-
- if self._owner_task is task:
- raise RuntimeError("Attempted to acquire an already held Lock")
-
- raise WouldBlock
-
- def locked(self) -> bool:
- return self._owner_task is not None
-
- def release(self) -> None:
- if self._owner_task != current_task():
- raise RuntimeError("The current task is not holding this lock")
-
- for task, fut in self._waiters:
- if not fut.cancelled():
- self._owner_task = task
- fut.set_result(None)
- return
-
- self._owner_task = None
-
- def statistics(self) -> LockStatistics:
- task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None
- return LockStatistics(self.locked(), task_info, len(self._waiters))
-
-
-class Semaphore(BaseSemaphore):
- def __new__(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> Semaphore:
- return object.__new__(cls)
-
- def __init__(
- self,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ):
- super().__init__(initial_value, max_value=max_value)
- self._value = initial_value
- self._max_value = max_value
- self._fast_acquire = fast_acquire
- self._waiters: deque[asyncio.Future[None]] = deque()
-
- async def acquire(self) -> None:
- if self._value > 0 and not self._waiters:
- await AsyncIOBackend.checkpoint_if_cancelled()
- self._value -= 1
-
- # Unless on the "fast path", yield control of the event loop so that other
- # tasks can run too
- if not self._fast_acquire:
- try:
- await AsyncIOBackend.cancel_shielded_checkpoint()
- except CancelledError:
- self.release()
- raise
-
- return
-
- fut: asyncio.Future[None] = asyncio.Future()
- self._waiters.append(fut)
- try:
- await fut
- except CancelledError:
- try:
- self._waiters.remove(fut)
- except ValueError:
- self.release()
-
- raise
-
- def acquire_nowait(self) -> None:
- if self._value == 0:
- raise WouldBlock
-
- self._value -= 1
-
- def release(self) -> None:
- if self._max_value is not None and self._value == self._max_value:
- raise ValueError("semaphore released too many times")
-
- for fut in self._waiters:
- if not fut.cancelled():
- fut.set_result(None)
- self._waiters.remove(fut)
- return
-
- self._value += 1
-
- @property
- def value(self) -> int:
- return self._value
-
- @property
- def max_value(self) -> int | None:
- return self._max_value
-
- def statistics(self) -> SemaphoreStatistics:
- return SemaphoreStatistics(len(self._waiters))
-
-
-class CapacityLimiter(BaseCapacityLimiter):
- _total_tokens: float = 0
-
- def __new__(cls, total_tokens: float) -> CapacityLimiter:
- return object.__new__(cls)
-
- def __init__(self, total_tokens: float):
- self._borrowers: set[Any] = set()
- self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict()
- self.total_tokens = total_tokens
-
- async def __aenter__(self) -> None:
- await self.acquire()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.release()
-
- @property
- def total_tokens(self) -> float:
- return self._total_tokens
-
- @total_tokens.setter
- def total_tokens(self, value: float) -> None:
- if not isinstance(value, int) and not math.isinf(value):
- raise TypeError("total_tokens must be an int or math.inf")
-
- if value < 0:
- raise ValueError("total_tokens must be >= 0")
-
- waiters_to_notify = max(value - self._total_tokens, 0)
- self._total_tokens = value
-
- # Notify waiting tasks that they have acquired the limiter
- while self._wait_queue and waiters_to_notify:
- event = self._wait_queue.popitem(last=False)[1]
- event.set()
- waiters_to_notify -= 1
-
- @property
- def borrowed_tokens(self) -> int:
- return len(self._borrowers)
-
- @property
- def available_tokens(self) -> float:
- return self._total_tokens - len(self._borrowers)
-
- def _notify_next_waiter(self) -> None:
- """Notify the next task in line if this limiter has free capacity now."""
- if self._wait_queue and len(self._borrowers) < self._total_tokens:
- event = self._wait_queue.popitem(last=False)[1]
- event.set()
-
- def acquire_nowait(self) -> None:
- self.acquire_on_behalf_of_nowait(current_task())
-
- def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
- if borrower in self._borrowers:
- raise RuntimeError(
- "this borrower is already holding one of this CapacityLimiter's tokens"
- )
-
- if self._wait_queue or len(self._borrowers) >= self._total_tokens:
- raise WouldBlock
-
- self._borrowers.add(borrower)
-
- async def acquire(self) -> None:
- return await self.acquire_on_behalf_of(current_task())
-
- async def acquire_on_behalf_of(self, borrower: object) -> None:
- await AsyncIOBackend.checkpoint_if_cancelled()
- try:
- self.acquire_on_behalf_of_nowait(borrower)
- except WouldBlock:
- event = asyncio.Event()
- self._wait_queue[borrower] = event
- try:
- await event.wait()
- except BaseException:
- self._wait_queue.pop(borrower, None)
- if event.is_set():
- self._notify_next_waiter()
-
- raise
-
- self._borrowers.add(borrower)
- else:
- try:
- await AsyncIOBackend.cancel_shielded_checkpoint()
- except BaseException:
- self.release()
- raise
-
- def release(self) -> None:
- self.release_on_behalf_of(current_task())
-
- def release_on_behalf_of(self, borrower: object) -> None:
- try:
- self._borrowers.remove(borrower)
- except KeyError:
- raise RuntimeError(
- "this borrower isn't holding any of this CapacityLimiter's tokens"
- ) from None
-
- self._notify_next_waiter()
-
- def statistics(self) -> CapacityLimiterStatistics:
- return CapacityLimiterStatistics(
- self.borrowed_tokens,
- self.total_tokens,
- tuple(self._borrowers),
- len(self._wait_queue),
- )
-
-
-_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter")
-
-
-#
-# Operating system signals
-#
-
-
-class _SignalReceiver:
- def __init__(self, signals: tuple[Signals, ...]):
- self._signals = signals
- self._loop = get_running_loop()
- self._signal_queue: deque[Signals] = deque()
- self._future: asyncio.Future = asyncio.Future()
- self._handled_signals: set[Signals] = set()
-
- def _deliver(self, signum: Signals) -> None:
- self._signal_queue.append(signum)
- if not self._future.done():
- self._future.set_result(None)
-
- def __enter__(self) -> _SignalReceiver:
- for sig in set(self._signals):
- self._loop.add_signal_handler(sig, self._deliver, sig)
- self._handled_signals.add(sig)
-
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- for sig in self._handled_signals:
- self._loop.remove_signal_handler(sig)
-
- def __aiter__(self) -> _SignalReceiver:
- return self
-
- async def __anext__(self) -> Signals:
- await AsyncIOBackend.checkpoint()
- if not self._signal_queue:
- self._future = asyncio.Future()
- await self._future
-
- return self._signal_queue.popleft()
-
-
-#
-# Testing and debugging
-#
-
-
-class AsyncIOTaskInfo(TaskInfo):
- def __init__(self, task: asyncio.Task):
- task_state = _task_states.get(task)
- if task_state is None:
- parent_id = None
- else:
- parent_id = task_state.parent_id
-
- coro = task.get_coro()
- assert coro is not None, "created TaskInfo from a completed Task"
- super().__init__(id(task), parent_id, task.get_name(), coro)
- self._task = weakref.ref(task)
-
- def has_pending_cancellation(self) -> bool:
- if not (task := self._task()):
- # If the task isn't around anymore, it won't have a pending cancellation
- return False
-
- if task._must_cancel: # type: ignore[attr-defined]
- return True
- elif (
- isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined]
- and task._fut_waiter.cancelled() # type: ignore[attr-defined]
- ):
- return True
-
- if task_state := _task_states.get(task):
- if cancel_scope := task_state.cancel_scope:
- return cancel_scope._effectively_cancelled
-
- return False
-
-
-class TestRunner(abc.TestRunner):
- _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]]
-
- def __init__(
- self,
- *,
- debug: bool | None = None,
- use_uvloop: bool = False,
- loop_factory: Callable[[], AbstractEventLoop] | None = None,
- ) -> None:
- if use_uvloop and loop_factory is None:
- if sys.platform != "win32":
- import uvloop
-
- loop_factory = uvloop.new_event_loop
- else:
- import winloop
-
- loop_factory = winloop.new_event_loop
-
- self._runner = Runner(debug=debug, loop_factory=loop_factory)
- self._exceptions: list[BaseException] = []
- self._runner_task: asyncio.Task | None = None
-
- def __enter__(self) -> TestRunner:
- self._runner.__enter__()
- self.get_loop().set_exception_handler(self._exception_handler)
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self._runner.__exit__(exc_type, exc_val, exc_tb)
-
- def get_loop(self) -> AbstractEventLoop:
- return self._runner.get_loop()
-
- def _exception_handler(
- self, loop: asyncio.AbstractEventLoop, context: dict[str, Any]
- ) -> None:
- if isinstance(context.get("exception"), Exception):
- self._exceptions.append(context["exception"])
- else:
- loop.default_exception_handler(context)
-
- def _raise_async_exceptions(self) -> None:
- # Re-raise any exceptions raised in asynchronous callbacks
- if self._exceptions:
- exceptions, self._exceptions = self._exceptions, []
- if len(exceptions) == 1:
- raise exceptions[0]
- elif exceptions:
- raise BaseExceptionGroup(
- "Multiple exceptions occurred in asynchronous callbacks", exceptions
- )
-
- async def _run_tests_and_fixtures(
- self,
- receive_stream: MemoryObjectReceiveStream[
- tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]]
- ],
- ) -> None:
- from _pytest.outcomes import OutcomeException
-
- with receive_stream, self._send_stream:
- async for coro, future in receive_stream:
- try:
- retval = await coro
- except CancelledError as exc:
- if not future.cancelled():
- future.cancel(*exc.args)
-
- raise
- except BaseException as exc:
- if not future.cancelled():
- future.set_exception(exc)
-
- if not isinstance(exc, (Exception, OutcomeException)):
- raise
- else:
- if not future.cancelled():
- future.set_result(retval)
-
- async def _call_in_runner_task(
- self,
- func: Callable[P, Awaitable[T_Retval]],
- *args: P.args,
- **kwargs: P.kwargs,
- ) -> T_Retval:
- if not self._runner_task:
- self._send_stream, receive_stream = create_memory_object_stream[
- tuple[Awaitable[Any], asyncio.Future]
- ](1)
- self._runner_task = self.get_loop().create_task(
- self._run_tests_and_fixtures(receive_stream)
- )
-
- coro = func(*args, **kwargs)
- future: asyncio.Future[T_Retval] = self.get_loop().create_future()
- self._send_stream.send_nowait((coro, future))
- return await future
-
- def run_asyncgen_fixture(
- self,
- fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
- kwargs: dict[str, Any],
- ) -> Iterable[T_Retval]:
- asyncgen = fixture_func(**kwargs)
- fixturevalue: T_Retval = self.get_loop().run_until_complete(
- self._call_in_runner_task(asyncgen.asend, None)
- )
- self._raise_async_exceptions()
-
- yield fixturevalue
-
- try:
- self.get_loop().run_until_complete(
- self._call_in_runner_task(asyncgen.asend, None)
- )
- except StopAsyncIteration:
- self._raise_async_exceptions()
- else:
- self.get_loop().run_until_complete(asyncgen.aclose())
- raise RuntimeError("Async generator fixture did not stop")
-
- def run_fixture(
- self,
- fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
- kwargs: dict[str, Any],
- ) -> T_Retval:
- retval = self.get_loop().run_until_complete(
- self._call_in_runner_task(fixture_func, **kwargs)
- )
- self._raise_async_exceptions()
- return retval
-
- def run_test(
- self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
- ) -> None:
- try:
- self.get_loop().run_until_complete(
- self._call_in_runner_task(test_func, **kwargs)
- )
- except Exception as exc:
- self._exceptions.append(exc)
-
- self._raise_async_exceptions()
-
-
-class AsyncIOBackend(AsyncBackend):
- @classmethod
- def run(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- kwargs: dict[str, Any],
- options: dict[str, Any],
- ) -> T_Retval:
- @wraps(func)
- async def wrapper() -> T_Retval:
- task = cast(asyncio.Task, current_task())
- task.set_name(get_callable_name(func))
- _task_states[task] = TaskState(None, None)
-
- try:
- return await func(*args)
- finally:
- del _task_states[task]
-
- debug = options.get("debug", None)
- loop_factory = options.get("loop_factory", None)
- if loop_factory is None and options.get("use_uvloop", False):
- if sys.platform != "win32":
- import uvloop
-
- loop_factory = uvloop.new_event_loop
- else:
- import winloop
-
- loop_factory = winloop.new_event_loop
-
- with Runner(debug=debug, loop_factory=loop_factory) as runner:
- return runner.run(wrapper())
-
- @classmethod
- def current_token(cls) -> object:
- return get_running_loop()
-
- @classmethod
- def current_time(cls) -> float:
- return get_running_loop().time()
-
- @classmethod
- def cancelled_exception_class(cls) -> type[BaseException]:
- return CancelledError
-
- @classmethod
- async def checkpoint(cls) -> None:
- await sleep(0)
-
- @classmethod
- async def checkpoint_if_cancelled(cls) -> None:
- task = current_task()
- if task is None:
- return
-
- try:
- cancel_scope = _task_states[task].cancel_scope
- except KeyError:
- return
-
- while cancel_scope:
- if cancel_scope.cancel_called:
- await sleep(0)
- elif cancel_scope.shield:
- break
- else:
- cancel_scope = cancel_scope._parent_scope
-
- @classmethod
- async def cancel_shielded_checkpoint(cls) -> None:
- with CancelScope(shield=True):
- await sleep(0)
-
- @classmethod
- async def sleep(cls, delay: float) -> None:
- await sleep(delay)
-
- @classmethod
- def create_cancel_scope(
- cls, *, deadline: float = math.inf, shield: bool = False
- ) -> CancelScope:
- return CancelScope(deadline=deadline, shield=shield)
-
- @classmethod
- def current_effective_deadline(cls) -> float:
- if (task := current_task()) is None:
- return math.inf
-
- try:
- cancel_scope = _task_states[task].cancel_scope
- except KeyError:
- return math.inf
-
- deadline = math.inf
- while cancel_scope:
- deadline = min(deadline, cancel_scope.deadline)
- if cancel_scope._cancel_called:
- deadline = -math.inf
- break
- elif cancel_scope.shield:
- break
- else:
- cancel_scope = cancel_scope._parent_scope
-
- return deadline
-
- @classmethod
- def create_task_group(cls) -> abc.TaskGroup:
- return TaskGroup()
-
- @classmethod
- def create_event(cls) -> abc.Event:
- return Event()
-
- @classmethod
- def create_lock(cls, *, fast_acquire: bool) -> abc.Lock:
- return Lock(fast_acquire=fast_acquire)
-
- @classmethod
- def create_semaphore(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> abc.Semaphore:
- return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
-
- @classmethod
- def create_capacity_limiter(cls, total_tokens: float) -> abc.CapacityLimiter:
- return CapacityLimiter(total_tokens)
-
- @classmethod
- async def run_sync_in_worker_thread( # type: ignore[return]
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- abandon_on_cancel: bool = False,
- limiter: abc.CapacityLimiter | None = None,
- ) -> T_Retval:
- await cls.checkpoint()
-
- # If this is the first run in this event loop thread, set up the necessary
- # variables
- try:
- idle_workers = _threadpool_idle_workers.get()
- workers = _threadpool_workers.get()
- except LookupError:
- idle_workers = deque()
- workers = set()
- _threadpool_idle_workers.set(idle_workers)
- _threadpool_workers.set(workers)
-
- async with limiter or cls.current_default_thread_limiter():
- with CancelScope(shield=not abandon_on_cancel) as scope:
- future = asyncio.Future[T_Retval]()
- root_task = find_root_task()
- if not idle_workers:
- worker = WorkerThread(root_task, workers, idle_workers)
- worker.start()
- workers.add(worker)
- root_task.add_done_callback(
- worker.stop, context=contextvars.Context()
- )
- else:
- worker = idle_workers.pop()
-
- # Prune any other workers that have been idle for MAX_IDLE_TIME
- # seconds or longer
- now = cls.current_time()
- while idle_workers:
- if (
- now - idle_workers[0].idle_since
- < WorkerThread.MAX_IDLE_TIME
- ):
- break
-
- expired_worker = idle_workers.popleft()
- expired_worker.root_task.remove_done_callback(
- expired_worker.stop
- )
- expired_worker.stop()
-
- context = copy_context()
- context.run(set_current_async_library, None)
- if abandon_on_cancel or scope._parent_scope is None:
- worker_scope = scope
- else:
- worker_scope = scope._parent_scope
-
- worker.queue.put_nowait((context, func, args, future, worker_scope))
- return await future
-
- @classmethod
- def check_cancelled(cls) -> None:
- scope: CancelScope | None = threadlocals.current_cancel_scope
- while scope is not None:
- if scope.cancel_called:
- raise CancelledError(f"Cancelled by cancel scope {id(scope):x}")
-
- if scope.shield:
- return
-
- scope = scope._parent_scope
-
- @classmethod
- def run_async_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- async def task_wrapper() -> T_Retval:
- __tracebackhide__ = True
- if scope is not None:
- task = cast(asyncio.Task, current_task())
- _task_states[task] = TaskState(None, scope)
- scope._tasks.add(task)
- try:
- return await func(*args)
- except CancelledError as exc:
- raise concurrent.futures.CancelledError(str(exc)) from None
- finally:
- if scope is not None:
- scope._tasks.discard(task)
-
- loop = cast(
- "AbstractEventLoop", token or threadlocals.current_token.native_token
- )
- if loop.is_closed():
- raise RunFinishedError
-
- context = copy_context()
- context.run(set_current_async_library, "asyncio")
- scope = getattr(threadlocals, "current_cancel_scope", None)
- f: concurrent.futures.Future[T_Retval] = context.run(
- asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop
- )
- return f.result()
-
- @classmethod
- def run_sync_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- @wraps(func)
- def wrapper() -> None:
- try:
- set_current_async_library("asyncio")
- f.set_result(func(*args))
- except BaseException as exc:
- f.set_exception(exc)
- if not isinstance(exc, Exception):
- raise
-
- loop = cast(
- "AbstractEventLoop", token or threadlocals.current_token.native_token
- )
- if loop.is_closed():
- raise RunFinishedError
-
- f: concurrent.futures.Future[T_Retval] = Future()
- loop.call_soon_threadsafe(wrapper)
- return f.result()
-
- @classmethod
- async def open_process(
- cls,
- command: StrOrBytesPath | Sequence[StrOrBytesPath],
- *,
- stdin: int | IO[Any] | None,
- stdout: int | IO[Any] | None,
- stderr: int | IO[Any] | None,
- **kwargs: Any,
- ) -> Process:
- await cls.checkpoint()
- if isinstance(command, PathLike):
- command = os.fspath(command)
-
- if isinstance(command, (str, bytes)):
- process = await asyncio.create_subprocess_shell(
- command,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- **kwargs,
- )
- else:
- process = await asyncio.create_subprocess_exec(
- *command,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- **kwargs,
- )
-
- stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None
- stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None
- stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None
- return Process(process, stdin_stream, stdout_stream, stderr_stream)
-
- @classmethod
- def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
- create_task(
- _shutdown_process_pool_on_exit(workers),
- name="AnyIO process pool shutdown task",
- )
- find_root_task().add_done_callback(
- partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type]
- )
-
- @classmethod
- async def connect_tcp(
- cls, host: str, port: int, local_address: IPSockAddrType | None = None
- ) -> abc.SocketStream:
- transport, protocol = cast(
- tuple[asyncio.Transport, StreamProtocol],
- await get_running_loop().create_connection(
- StreamProtocol, host, port, local_addr=local_address
- ),
- )
- transport.pause_reading()
- return SocketStream(transport, protocol)
-
- @classmethod
- async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
- await cls.checkpoint()
- loop = get_running_loop()
- raw_socket = socket.socket(socket.AF_UNIX)
- raw_socket.setblocking(False)
- while True:
- try:
- raw_socket.connect(path)
- except BlockingIOError:
- f: asyncio.Future = asyncio.Future()
- loop.add_writer(raw_socket, f.set_result, None)
- f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
- await f
- except BaseException:
- raw_socket.close()
- raise
- else:
- return UNIXSocketStream(raw_socket)
-
- @classmethod
- def create_tcp_listener(cls, sock: socket.socket) -> SocketListener:
- return TCPSocketListener(sock)
-
- @classmethod
- def create_unix_listener(cls, sock: socket.socket) -> SocketListener:
- return UNIXSocketListener(sock)
-
- @classmethod
- async def create_udp_socket(
- cls,
- family: AddressFamily,
- local_address: IPSockAddrType | None,
- remote_address: IPSockAddrType | None,
- reuse_port: bool,
- ) -> UDPSocket | ConnectedUDPSocket:
- transport, protocol = await get_running_loop().create_datagram_endpoint(
- DatagramProtocol,
- local_addr=local_address,
- remote_addr=remote_address,
- family=family,
- reuse_port=reuse_port,
- )
- if protocol.exception:
- transport.close()
- raise protocol.exception
-
- if not remote_address:
- return UDPSocket(transport, protocol)
- else:
- return ConnectedUDPSocket(transport, protocol)
-
- @classmethod
- async def create_unix_datagram_socket( # type: ignore[override]
- cls, raw_socket: socket.socket, remote_path: str | bytes | None
- ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
- await cls.checkpoint()
- loop = get_running_loop()
-
- if remote_path:
- while True:
- try:
- raw_socket.connect(remote_path)
- except BlockingIOError:
- f: asyncio.Future = asyncio.Future()
- loop.add_writer(raw_socket, f.set_result, None)
- f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
- await f
- except BaseException:
- raw_socket.close()
- raise
- else:
- return ConnectedUNIXDatagramSocket(raw_socket)
- else:
- return UNIXDatagramSocket(raw_socket)
-
- @classmethod
- async def getaddrinfo(
- cls,
- host: bytes | str | None,
- port: str | int | None,
- *,
- family: int | AddressFamily = 0,
- type: int | SocketKind = 0,
- proto: int = 0,
- flags: int = 0,
- ) -> Sequence[
- tuple[
- AddressFamily,
- SocketKind,
- int,
- str,
- tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
- ]
- ]:
- return await get_running_loop().getaddrinfo(
- host, port, family=family, type=type, proto=proto, flags=flags
- )
-
- @classmethod
- async def getnameinfo(
- cls, sockaddr: IPSockAddrType, flags: int = 0
- ) -> tuple[str, str]:
- return await get_running_loop().getnameinfo(sockaddr, flags)
-
- @classmethod
- async def wait_readable(cls, obj: FileDescriptorLike) -> None:
- try:
- read_events = _read_events.get()
- except LookupError:
- read_events = {}
- _read_events.set(read_events)
-
- fd = obj if isinstance(obj, int) else obj.fileno()
- if read_events.get(fd):
- raise BusyResourceError("reading from")
-
- loop = get_running_loop()
- fut: asyncio.Future[bool] = loop.create_future()
-
- def cb() -> None:
- try:
- del read_events[fd]
- except KeyError:
- pass
- else:
- remove_reader(fd)
-
- try:
- fut.set_result(True)
- except asyncio.InvalidStateError:
- pass
-
- try:
- loop.add_reader(fd, cb)
- except NotImplementedError:
- from anyio._core._asyncio_selector_thread import get_selector
-
- selector = get_selector()
- selector.add_reader(fd, cb)
- remove_reader = selector.remove_reader
- else:
- remove_reader = loop.remove_reader
-
- read_events[fd] = fut
- try:
- success = await fut
- finally:
- try:
- del read_events[fd]
- except KeyError:
- pass
- else:
- remove_reader(fd)
-
- if not success:
- raise ClosedResourceError
-
- @classmethod
- async def wait_writable(cls, obj: FileDescriptorLike) -> None:
- try:
- write_events = _write_events.get()
- except LookupError:
- write_events = {}
- _write_events.set(write_events)
-
- fd = obj if isinstance(obj, int) else obj.fileno()
- if write_events.get(fd):
- raise BusyResourceError("writing to")
-
- loop = get_running_loop()
- fut: asyncio.Future[bool] = loop.create_future()
-
- def cb() -> None:
- try:
- del write_events[fd]
- except KeyError:
- pass
- else:
- remove_writer(fd)
-
- try:
- fut.set_result(True)
- except asyncio.InvalidStateError:
- pass
-
- try:
- loop.add_writer(fd, cb)
- except NotImplementedError:
- from anyio._core._asyncio_selector_thread import get_selector
-
- selector = get_selector()
- selector.add_writer(fd, cb)
- remove_writer = selector.remove_writer
- else:
- remove_writer = loop.remove_writer
-
- write_events[fd] = fut
- try:
- success = await fut
- finally:
- try:
- del write_events[fd]
- except KeyError:
- pass
- else:
- remove_writer(fd)
-
- if not success:
- raise ClosedResourceError
-
- @classmethod
- def notify_closing(cls, obj: FileDescriptorLike) -> None:
- fd = obj if isinstance(obj, int) else obj.fileno()
- loop = get_running_loop()
-
- try:
- write_events = _write_events.get()
- except LookupError:
- pass
- else:
- try:
- fut = write_events.pop(fd)
- except KeyError:
- pass
- else:
- try:
- fut.set_result(False)
- except asyncio.InvalidStateError:
- pass
-
- try:
- loop.remove_writer(fd)
- except NotImplementedError:
- from anyio._core._asyncio_selector_thread import get_selector
-
- get_selector().remove_writer(fd)
-
- try:
- read_events = _read_events.get()
- except LookupError:
- pass
- else:
- try:
- fut = read_events.pop(fd)
- except KeyError:
- pass
- else:
- try:
- fut.set_result(False)
- except asyncio.InvalidStateError:
- pass
-
- try:
- loop.remove_reader(fd)
- except NotImplementedError:
- from anyio._core._asyncio_selector_thread import get_selector
-
- get_selector().remove_reader(fd)
-
- @classmethod
- async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener:
- return TCPSocketListener(sock)
-
- @classmethod
- async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
- transport, protocol = await get_running_loop().create_connection(
- StreamProtocol, sock=sock
- )
- return SocketStream(transport, protocol)
-
- @classmethod
- async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
- return UNIXSocketStream(sock)
-
- @classmethod
- async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
- transport, protocol = await get_running_loop().create_datagram_endpoint(
- DatagramProtocol, sock=sock
- )
- return UDPSocket(transport, protocol)
-
- @classmethod
- async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
- transport, protocol = await get_running_loop().create_datagram_endpoint(
- DatagramProtocol, sock=sock
- )
- return ConnectedUDPSocket(transport, protocol)
-
- @classmethod
- async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
- return UNIXDatagramSocket(sock)
-
- @classmethod
- async def wrap_connected_unix_datagram_socket(
- cls, sock: socket.socket
- ) -> ConnectedUNIXDatagramSocket:
- return ConnectedUNIXDatagramSocket(sock)
-
- @classmethod
- def current_default_thread_limiter(cls) -> CapacityLimiter:
- try:
- return _default_thread_limiter.get()
- except LookupError:
- limiter = CapacityLimiter(40)
- _default_thread_limiter.set(limiter)
- return limiter
-
- @classmethod
- def open_signal_receiver(
- cls, *signals: Signals
- ) -> AbstractContextManager[AsyncIterator[Signals]]:
- return _SignalReceiver(signals)
-
- @classmethod
- def get_current_task(cls) -> TaskInfo:
- return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type]
-
- @classmethod
- def get_running_tasks(cls) -> Sequence[TaskInfo]:
- return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()]
-
- @classmethod
- async def wait_all_tasks_blocked(cls) -> None:
- await cls.checkpoint()
- this_task = current_task()
- while True:
- for task in all_tasks():
- if task is this_task:
- continue
-
- waiter = task._fut_waiter # type: ignore[attr-defined]
- if waiter is None or waiter.done():
- await sleep(0.1)
- break
- else:
- return
-
- @classmethod
- def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
- return TestRunner(**options)
-
-
-backend_class = AsyncIOBackend
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_trio.py b/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_trio.py
deleted file mode 100644
index f460a7f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_backends/_trio.py
+++ /dev/null
@@ -1,1346 +0,0 @@
-from __future__ import annotations
-
-import array
-import math
-import os
-import socket
-import sys
-import types
-import weakref
-from collections.abc import (
- AsyncGenerator,
- AsyncIterator,
- Awaitable,
- Callable,
- Collection,
- Coroutine,
- Iterable,
- Sequence,
-)
-from contextlib import AbstractContextManager
-from dataclasses import dataclass
-from io import IOBase
-from os import PathLike
-from signal import Signals
-from socket import AddressFamily, SocketKind
-from types import TracebackType
-from typing import (
- IO,
- TYPE_CHECKING,
- Any,
- Generic,
- NoReturn,
- TypeVar,
- cast,
- overload,
-)
-
-import trio.from_thread
-import trio.lowlevel
-from outcome import Error, Outcome, Value
-from trio.lowlevel import (
- current_root_task,
- current_task,
- notify_closing,
- wait_readable,
- wait_writable,
-)
-from trio.socket import SocketType as TrioSocketType
-from trio.to_thread import run_sync
-
-from .. import (
- CapacityLimiterStatistics,
- EventStatistics,
- LockStatistics,
- RunFinishedError,
- TaskInfo,
- WouldBlock,
- abc,
-)
-from .._core._eventloop import claim_worker_thread
-from .._core._exceptions import (
- BrokenResourceError,
- BusyResourceError,
- ClosedResourceError,
- EndOfStream,
-)
-from .._core._sockets import convert_ipv6_sockaddr
-from .._core._streams import create_memory_object_stream
-from .._core._synchronization import (
- CapacityLimiter as BaseCapacityLimiter,
-)
-from .._core._synchronization import Event as BaseEvent
-from .._core._synchronization import Lock as BaseLock
-from .._core._synchronization import (
- ResourceGuard,
- SemaphoreStatistics,
-)
-from .._core._synchronization import Semaphore as BaseSemaphore
-from .._core._tasks import CancelScope as BaseCancelScope
-from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType
-from ..abc._eventloop import AsyncBackend, StrOrBytesPath
-from ..streams.memory import MemoryObjectSendStream
-
-if TYPE_CHECKING:
- from _typeshed import FileDescriptorLike
-
-if sys.version_info >= (3, 10):
- from typing import ParamSpec
-else:
- from typing_extensions import ParamSpec
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from exceptiongroup import BaseExceptionGroup
- from typing_extensions import TypeVarTuple, Unpack
-
-T = TypeVar("T")
-T_Retval = TypeVar("T_Retval")
-T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType)
-PosArgsT = TypeVarTuple("PosArgsT")
-P = ParamSpec("P")
-
-
-#
-# Event loop
-#
-
-RunVar = trio.lowlevel.RunVar
-
-
-#
-# Timeouts and cancellation
-#
-
-
-class CancelScope(BaseCancelScope):
- def __new__(
- cls, original: trio.CancelScope | None = None, **kwargs: object
- ) -> CancelScope:
- return object.__new__(cls)
-
- def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None:
- self.__original = original or trio.CancelScope(**kwargs)
-
- def __enter__(self) -> CancelScope:
- self.__original.__enter__()
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- return self.__original.__exit__(exc_type, exc_val, exc_tb)
-
- def cancel(self, reason: str | None = None) -> None:
- self.__original.cancel(reason)
-
- @property
- def deadline(self) -> float:
- return self.__original.deadline
-
- @deadline.setter
- def deadline(self, value: float) -> None:
- self.__original.deadline = value
-
- @property
- def cancel_called(self) -> bool:
- return self.__original.cancel_called
-
- @property
- def cancelled_caught(self) -> bool:
- return self.__original.cancelled_caught
-
- @property
- def shield(self) -> bool:
- return self.__original.shield
-
- @shield.setter
- def shield(self, value: bool) -> None:
- self.__original.shield = value
-
-
-#
-# Task groups
-#
-
-
-class TaskGroup(abc.TaskGroup):
- def __init__(self) -> None:
- self._active = False
- self._nursery_manager = trio.open_nursery(strict_exception_groups=True)
- self.cancel_scope = None # type: ignore[assignment]
-
- async def __aenter__(self) -> TaskGroup:
- self._active = True
- self._nursery = await self._nursery_manager.__aenter__()
- self.cancel_scope = CancelScope(self._nursery.cancel_scope)
- return self
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- try:
- # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type
- return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value]
- except BaseExceptionGroup as exc:
- if not exc.split(trio.Cancelled)[1]:
- raise trio.Cancelled._create() from exc
-
- raise
- finally:
- del exc_val, exc_tb
- self._active = False
-
- def start_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[Any]],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> None:
- if not self._active:
- raise RuntimeError(
- "This task group is not active; no new tasks can be started."
- )
-
- self._nursery.start_soon(func, *args, name=name)
-
- async def start(
- self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None
- ) -> Any:
- if not self._active:
- raise RuntimeError(
- "This task group is not active; no new tasks can be started."
- )
-
- return await self._nursery.start(func, *args, name=name)
-
-
-#
-# Subprocesses
-#
-
-
-@dataclass(eq=False)
-class ReceiveStreamWrapper(abc.ByteReceiveStream):
- _stream: trio.abc.ReceiveStream
-
- async def receive(self, max_bytes: int | None = None) -> bytes:
- try:
- data = await self._stream.receive_some(max_bytes)
- except trio.ClosedResourceError as exc:
- raise ClosedResourceError from exc.__cause__
- except trio.BrokenResourceError as exc:
- raise BrokenResourceError from exc.__cause__
-
- if data:
- return bytes(data)
- else:
- raise EndOfStream
-
- async def aclose(self) -> None:
- await self._stream.aclose()
-
-
-@dataclass(eq=False)
-class SendStreamWrapper(abc.ByteSendStream):
- _stream: trio.abc.SendStream
-
- async def send(self, item: bytes) -> None:
- try:
- await self._stream.send_all(item)
- except trio.ClosedResourceError as exc:
- raise ClosedResourceError from exc.__cause__
- except trio.BrokenResourceError as exc:
- raise BrokenResourceError from exc.__cause__
-
- async def aclose(self) -> None:
- await self._stream.aclose()
-
-
-@dataclass(eq=False)
-class Process(abc.Process):
- _process: trio.Process
- _stdin: abc.ByteSendStream | None
- _stdout: abc.ByteReceiveStream | None
- _stderr: abc.ByteReceiveStream | None
-
- async def aclose(self) -> None:
- with CancelScope(shield=True):
- if self._stdin:
- await self._stdin.aclose()
- if self._stdout:
- await self._stdout.aclose()
- if self._stderr:
- await self._stderr.aclose()
-
- try:
- await self.wait()
- except BaseException:
- self.kill()
- with CancelScope(shield=True):
- await self.wait()
- raise
-
- async def wait(self) -> int:
- return await self._process.wait()
-
- def terminate(self) -> None:
- self._process.terminate()
-
- def kill(self) -> None:
- self._process.kill()
-
- def send_signal(self, signal: Signals) -> None:
- self._process.send_signal(signal)
-
- @property
- def pid(self) -> int:
- return self._process.pid
-
- @property
- def returncode(self) -> int | None:
- return self._process.returncode
-
- @property
- def stdin(self) -> abc.ByteSendStream | None:
- return self._stdin
-
- @property
- def stdout(self) -> abc.ByteReceiveStream | None:
- return self._stdout
-
- @property
- def stderr(self) -> abc.ByteReceiveStream | None:
- return self._stderr
-
-
-class _ProcessPoolShutdownInstrument(trio.abc.Instrument):
- def after_run(self) -> None:
- super().after_run()
-
-
-current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar(
- "current_default_worker_process_limiter"
-)
-
-
-async def _shutdown_process_pool(workers: set[abc.Process]) -> None:
- try:
- await trio.sleep(math.inf)
- except trio.Cancelled:
- for process in workers:
- if process.returncode is None:
- process.kill()
-
- with CancelScope(shield=True):
- for process in workers:
- await process.aclose()
-
-
-#
-# Sockets and networking
-#
-
-
-class _TrioSocketMixin(Generic[T_SockAddr]):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- self._trio_socket = trio_socket
- self._closed = False
-
- def _check_closed(self) -> None:
- if self._closed:
- raise ClosedResourceError
- if self._trio_socket.fileno() < 0:
- raise BrokenResourceError
-
- @property
- def _raw_socket(self) -> socket.socket:
- return self._trio_socket._sock # type: ignore[attr-defined]
-
- async def aclose(self) -> None:
- if self._trio_socket.fileno() >= 0:
- self._closed = True
- self._trio_socket.close()
-
- def _convert_socket_error(self, exc: BaseException) -> NoReturn:
- if isinstance(exc, trio.ClosedResourceError):
- raise ClosedResourceError from exc
- elif self._trio_socket.fileno() < 0 and self._closed:
- raise ClosedResourceError from None
- elif isinstance(exc, OSError):
- raise BrokenResourceError from exc
- else:
- raise exc
-
-
-class SocketStream(_TrioSocketMixin, abc.SocketStream):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- super().__init__(trio_socket)
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- with self._receive_guard:
- try:
- data = await self._trio_socket.recv(max_bytes)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- if data:
- return data
- else:
- raise EndOfStream
-
- async def send(self, item: bytes) -> None:
- with self._send_guard:
- view = memoryview(item)
- while view:
- try:
- bytes_sent = await self._trio_socket.send(view)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- view = view[bytes_sent:]
-
- async def send_eof(self) -> None:
- self._trio_socket.shutdown(socket.SHUT_WR)
-
-
-class UNIXSocketStream(SocketStream, abc.UNIXSocketStream):
- async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
- if not isinstance(msglen, int) or msglen < 0:
- raise ValueError("msglen must be a non-negative integer")
- if not isinstance(maxfds, int) or maxfds < 1:
- raise ValueError("maxfds must be a positive integer")
-
- fds = array.array("i")
- await trio.lowlevel.checkpoint()
- with self._receive_guard:
- while True:
- try:
- message, ancdata, flags, addr = await self._trio_socket.recvmsg(
- msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
- )
- except BaseException as exc:
- self._convert_socket_error(exc)
- else:
- if not message and not ancdata:
- raise EndOfStream
-
- break
-
- for cmsg_level, cmsg_type, cmsg_data in ancdata:
- if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
- raise RuntimeError(
- f"Received unexpected ancillary data; message = {message!r}, "
- f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
- )
-
- fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
-
- return message, list(fds)
-
- async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
- if not message:
- raise ValueError("message must not be empty")
- if not fds:
- raise ValueError("fds must not be empty")
-
- filenos: list[int] = []
- for fd in fds:
- if isinstance(fd, int):
- filenos.append(fd)
- elif isinstance(fd, IOBase):
- filenos.append(fd.fileno())
-
- fdarray = array.array("i", filenos)
- await trio.lowlevel.checkpoint()
- with self._send_guard:
- while True:
- try:
- await self._trio_socket.sendmsg(
- [message],
- [
- (
- socket.SOL_SOCKET,
- socket.SCM_RIGHTS,
- fdarray,
- )
- ],
- )
- break
- except BaseException as exc:
- self._convert_socket_error(exc)
-
-
-class TCPSocketListener(_TrioSocketMixin, abc.SocketListener):
- def __init__(self, raw_socket: socket.socket):
- super().__init__(trio.socket.from_stdlib_socket(raw_socket))
- self._accept_guard = ResourceGuard("accepting connections from")
-
- async def accept(self) -> SocketStream:
- with self._accept_guard:
- try:
- trio_socket, _addr = await self._trio_socket.accept()
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- return SocketStream(trio_socket)
-
-
-class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener):
- def __init__(self, raw_socket: socket.socket):
- super().__init__(trio.socket.from_stdlib_socket(raw_socket))
- self._accept_guard = ResourceGuard("accepting connections from")
-
- async def accept(self) -> UNIXSocketStream:
- with self._accept_guard:
- try:
- trio_socket, _addr = await self._trio_socket.accept()
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- return UNIXSocketStream(trio_socket)
-
-
-class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- super().__init__(trio_socket)
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- async def receive(self) -> tuple[bytes, IPSockAddrType]:
- with self._receive_guard:
- try:
- data, addr = await self._trio_socket.recvfrom(65536)
- return data, convert_ipv6_sockaddr(addr)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- async def send(self, item: UDPPacketType) -> None:
- with self._send_guard:
- try:
- await self._trio_socket.sendto(*item)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
-
-class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- super().__init__(trio_socket)
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- async def receive(self) -> bytes:
- with self._receive_guard:
- try:
- return await self._trio_socket.recv(65536)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- async def send(self, item: bytes) -> None:
- with self._send_guard:
- try:
- await self._trio_socket.send(item)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
-
-class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- super().__init__(trio_socket)
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- async def receive(self) -> UNIXDatagramPacketType:
- with self._receive_guard:
- try:
- data, addr = await self._trio_socket.recvfrom(65536)
- return data, addr
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- async def send(self, item: UNIXDatagramPacketType) -> None:
- with self._send_guard:
- try:
- await self._trio_socket.sendto(*item)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
-
-class ConnectedUNIXDatagramSocket(
- _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket
-):
- def __init__(self, trio_socket: TrioSocketType) -> None:
- super().__init__(trio_socket)
- self._receive_guard = ResourceGuard("reading from")
- self._send_guard = ResourceGuard("writing to")
-
- async def receive(self) -> bytes:
- with self._receive_guard:
- try:
- return await self._trio_socket.recv(65536)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
- async def send(self, item: bytes) -> None:
- with self._send_guard:
- try:
- await self._trio_socket.send(item)
- except BaseException as exc:
- self._convert_socket_error(exc)
-
-
-#
-# Synchronization
-#
-
-
-class Event(BaseEvent):
- def __new__(cls) -> Event:
- return object.__new__(cls)
-
- def __init__(self) -> None:
- self.__original = trio.Event()
-
- def is_set(self) -> bool:
- return self.__original.is_set()
-
- async def wait(self) -> None:
- return await self.__original.wait()
-
- def statistics(self) -> EventStatistics:
- orig_statistics = self.__original.statistics()
- return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting)
-
- def set(self) -> None:
- self.__original.set()
-
-
-class Lock(BaseLock):
- def __new__(cls, *, fast_acquire: bool = False) -> Lock:
- return object.__new__(cls)
-
- def __init__(self, *, fast_acquire: bool = False) -> None:
- self._fast_acquire = fast_acquire
- self.__original = trio.Lock()
-
- @staticmethod
- def _convert_runtime_error_msg(exc: RuntimeError) -> None:
- if exc.args == ("attempt to re-acquire an already held Lock",):
- exc.args = ("Attempted to acquire an already held Lock",)
-
- async def acquire(self) -> None:
- if not self._fast_acquire:
- try:
- await self.__original.acquire()
- except RuntimeError as exc:
- self._convert_runtime_error_msg(exc)
- raise
-
- return
-
- # This is the "fast path" where we don't let other tasks run
- await trio.lowlevel.checkpoint_if_cancelled()
- try:
- self.__original.acquire_nowait()
- except trio.WouldBlock:
- await self.__original._lot.park()
- except RuntimeError as exc:
- self._convert_runtime_error_msg(exc)
- raise
-
- def acquire_nowait(self) -> None:
- try:
- self.__original.acquire_nowait()
- except trio.WouldBlock:
- raise WouldBlock from None
- except RuntimeError as exc:
- self._convert_runtime_error_msg(exc)
- raise
-
- def locked(self) -> bool:
- return self.__original.locked()
-
- def release(self) -> None:
- self.__original.release()
-
- def statistics(self) -> LockStatistics:
- orig_statistics = self.__original.statistics()
- owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None
- return LockStatistics(
- orig_statistics.locked, owner, orig_statistics.tasks_waiting
- )
-
-
-class Semaphore(BaseSemaphore):
- def __new__(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> Semaphore:
- return object.__new__(cls)
-
- def __init__(
- self,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> None:
- super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
- self.__original = trio.Semaphore(initial_value, max_value=max_value)
-
- async def acquire(self) -> None:
- if not self._fast_acquire:
- await self.__original.acquire()
- return
-
- # This is the "fast path" where we don't let other tasks run
- await trio.lowlevel.checkpoint_if_cancelled()
- try:
- self.__original.acquire_nowait()
- except trio.WouldBlock:
- await self.__original._lot.park()
-
- def acquire_nowait(self) -> None:
- try:
- self.__original.acquire_nowait()
- except trio.WouldBlock:
- raise WouldBlock from None
-
- @property
- def max_value(self) -> int | None:
- return self.__original.max_value
-
- @property
- def value(self) -> int:
- return self.__original.value
-
- def release(self) -> None:
- self.__original.release()
-
- def statistics(self) -> SemaphoreStatistics:
- orig_statistics = self.__original.statistics()
- return SemaphoreStatistics(orig_statistics.tasks_waiting)
-
-
-class CapacityLimiter(BaseCapacityLimiter):
- def __new__(
- cls,
- total_tokens: float | None = None,
- *,
- original: trio.CapacityLimiter | None = None,
- ) -> CapacityLimiter:
- return object.__new__(cls)
-
- def __init__(
- self,
- total_tokens: float | None = None,
- *,
- original: trio.CapacityLimiter | None = None,
- ) -> None:
- if original is not None:
- self.__original = original
- else:
- assert total_tokens is not None
- self.__original = trio.CapacityLimiter(total_tokens)
-
- async def __aenter__(self) -> None:
- return await self.__original.__aenter__()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- await self.__original.__aexit__(exc_type, exc_val, exc_tb)
-
- @property
- def total_tokens(self) -> float:
- return self.__original.total_tokens
-
- @total_tokens.setter
- def total_tokens(self, value: float) -> None:
- self.__original.total_tokens = value
-
- @property
- def borrowed_tokens(self) -> int:
- return self.__original.borrowed_tokens
-
- @property
- def available_tokens(self) -> float:
- return self.__original.available_tokens
-
- def acquire_nowait(self) -> None:
- self.__original.acquire_nowait()
-
- def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
- self.__original.acquire_on_behalf_of_nowait(borrower)
-
- async def acquire(self) -> None:
- await self.__original.acquire()
-
- async def acquire_on_behalf_of(self, borrower: object) -> None:
- await self.__original.acquire_on_behalf_of(borrower)
-
- def release(self) -> None:
- return self.__original.release()
-
- def release_on_behalf_of(self, borrower: object) -> None:
- return self.__original.release_on_behalf_of(borrower)
-
- def statistics(self) -> CapacityLimiterStatistics:
- orig = self.__original.statistics()
- return CapacityLimiterStatistics(
- borrowed_tokens=orig.borrowed_tokens,
- total_tokens=orig.total_tokens,
- borrowers=tuple(orig.borrowers),
- tasks_waiting=orig.tasks_waiting,
- )
-
-
-_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper")
-
-
-#
-# Signal handling
-#
-
-
-class _SignalReceiver:
- _iterator: AsyncIterator[int]
-
- def __init__(self, signals: tuple[Signals, ...]):
- self._signals = signals
-
- def __enter__(self) -> _SignalReceiver:
- self._cm = trio.open_signal_receiver(*self._signals)
- self._iterator = self._cm.__enter__()
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool | None:
- return self._cm.__exit__(exc_type, exc_val, exc_tb)
-
- def __aiter__(self) -> _SignalReceiver:
- return self
-
- async def __anext__(self) -> Signals:
- signum = await self._iterator.__anext__()
- return Signals(signum)
-
-
-#
-# Testing and debugging
-#
-
-
-class TestRunner(abc.TestRunner):
- def __init__(self, **options: Any) -> None:
- from queue import Queue
-
- self._call_queue: Queue[Callable[[], object]] = Queue()
- self._send_stream: MemoryObjectSendStream | None = None
- self._options = options
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: types.TracebackType | None,
- ) -> None:
- if self._send_stream:
- self._send_stream.close()
- while self._send_stream is not None:
- self._call_queue.get()()
-
- async def _run_tests_and_fixtures(self) -> None:
- self._send_stream, receive_stream = create_memory_object_stream(1)
- with receive_stream:
- async for coro, outcome_holder in receive_stream:
- try:
- retval = await coro
- except BaseException as exc:
- outcome_holder.append(Error(exc))
- else:
- outcome_holder.append(Value(retval))
-
- def _main_task_finished(self, outcome: object) -> None:
- self._send_stream = None
-
- def _call_in_runner_task(
- self,
- func: Callable[P, Awaitable[T_Retval]],
- *args: P.args,
- **kwargs: P.kwargs,
- ) -> T_Retval:
- if self._send_stream is None:
- trio.lowlevel.start_guest_run(
- self._run_tests_and_fixtures,
- run_sync_soon_threadsafe=self._call_queue.put,
- done_callback=self._main_task_finished,
- **self._options,
- )
- while self._send_stream is None:
- self._call_queue.get()()
-
- outcome_holder: list[Outcome] = []
- self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder))
- while not outcome_holder:
- self._call_queue.get()()
-
- return outcome_holder[0].unwrap()
-
- def run_asyncgen_fixture(
- self,
- fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
- kwargs: dict[str, Any],
- ) -> Iterable[T_Retval]:
- asyncgen = fixture_func(**kwargs)
- fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None)
-
- yield fixturevalue
-
- try:
- self._call_in_runner_task(asyncgen.asend, None)
- except StopAsyncIteration:
- pass
- else:
- self._call_in_runner_task(asyncgen.aclose)
- raise RuntimeError("Async generator fixture did not stop")
-
- def run_fixture(
- self,
- fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
- kwargs: dict[str, Any],
- ) -> T_Retval:
- return self._call_in_runner_task(fixture_func, **kwargs)
-
- def run_test(
- self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
- ) -> None:
- self._call_in_runner_task(test_func, **kwargs)
-
-
-class TrioTaskInfo(TaskInfo):
- def __init__(self, task: trio.lowlevel.Task):
- parent_id = None
- if task.parent_nursery and task.parent_nursery.parent_task:
- parent_id = id(task.parent_nursery.parent_task)
-
- super().__init__(id(task), parent_id, task.name, task.coro)
- self._task = weakref.proxy(task)
-
- def has_pending_cancellation(self) -> bool:
- try:
- return self._task._cancel_status.effectively_cancelled
- except ReferenceError:
- # If the task is no longer around, it surely doesn't have a cancellation
- # pending
- return False
-
-
-class TrioBackend(AsyncBackend):
- @classmethod
- def run(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- kwargs: dict[str, Any],
- options: dict[str, Any],
- ) -> T_Retval:
- return trio.run(func, *args)
-
- @classmethod
- def current_token(cls) -> object:
- return trio.lowlevel.current_trio_token()
-
- @classmethod
- def current_time(cls) -> float:
- return trio.current_time()
-
- @classmethod
- def cancelled_exception_class(cls) -> type[BaseException]:
- return trio.Cancelled
-
- @classmethod
- async def checkpoint(cls) -> None:
- await trio.lowlevel.checkpoint()
-
- @classmethod
- async def checkpoint_if_cancelled(cls) -> None:
- await trio.lowlevel.checkpoint_if_cancelled()
-
- @classmethod
- async def cancel_shielded_checkpoint(cls) -> None:
- await trio.lowlevel.cancel_shielded_checkpoint()
-
- @classmethod
- async def sleep(cls, delay: float) -> None:
- await trio.sleep(delay)
-
- @classmethod
- def create_cancel_scope(
- cls, *, deadline: float = math.inf, shield: bool = False
- ) -> abc.CancelScope:
- return CancelScope(deadline=deadline, shield=shield)
-
- @classmethod
- def current_effective_deadline(cls) -> float:
- return trio.current_effective_deadline()
-
- @classmethod
- def create_task_group(cls) -> abc.TaskGroup:
- return TaskGroup()
-
- @classmethod
- def create_event(cls) -> abc.Event:
- return Event()
-
- @classmethod
- def create_lock(cls, *, fast_acquire: bool) -> Lock:
- return Lock(fast_acquire=fast_acquire)
-
- @classmethod
- def create_semaphore(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> abc.Semaphore:
- return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
-
- @classmethod
- def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter:
- return CapacityLimiter(total_tokens)
-
- @classmethod
- async def run_sync_in_worker_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- abandon_on_cancel: bool = False,
- limiter: abc.CapacityLimiter | None = None,
- ) -> T_Retval:
- def wrapper() -> T_Retval:
- with claim_worker_thread(TrioBackend, token):
- return func(*args)
-
- token = TrioBackend.current_token()
- return await run_sync(
- wrapper,
- abandon_on_cancel=abandon_on_cancel,
- limiter=cast(trio.CapacityLimiter, limiter),
- )
-
- @classmethod
- def check_cancelled(cls) -> None:
- trio.from_thread.check_cancelled()
-
- @classmethod
- def run_async_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- trio_token = cast("trio.lowlevel.TrioToken | None", token)
- try:
- return trio.from_thread.run(func, *args, trio_token=trio_token)
- except trio.RunFinishedError:
- raise RunFinishedError from None
-
- @classmethod
- def run_sync_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- trio_token = cast("trio.lowlevel.TrioToken | None", token)
- try:
- return trio.from_thread.run_sync(func, *args, trio_token=trio_token)
- except trio.RunFinishedError:
- raise RunFinishedError from None
-
- @classmethod
- async def open_process(
- cls,
- command: StrOrBytesPath | Sequence[StrOrBytesPath],
- *,
- stdin: int | IO[Any] | None,
- stdout: int | IO[Any] | None,
- stderr: int | IO[Any] | None,
- **kwargs: Any,
- ) -> Process:
- def convert_item(item: StrOrBytesPath) -> str:
- str_or_bytes = os.fspath(item)
- if isinstance(str_or_bytes, str):
- return str_or_bytes
- else:
- return os.fsdecode(str_or_bytes)
-
- if isinstance(command, (str, bytes, PathLike)):
- process = await trio.lowlevel.open_process(
- convert_item(command),
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- shell=True,
- **kwargs,
- )
- else:
- process = await trio.lowlevel.open_process(
- [convert_item(item) for item in command],
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- shell=False,
- **kwargs,
- )
-
- stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None
- stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None
- stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None
- return Process(process, stdin_stream, stdout_stream, stderr_stream)
-
- @classmethod
- def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
- trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers)
-
- @classmethod
- async def connect_tcp(
- cls, host: str, port: int, local_address: IPSockAddrType | None = None
- ) -> SocketStream:
- family = socket.AF_INET6 if ":" in host else socket.AF_INET
- trio_socket = trio.socket.socket(family)
- trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- if local_address:
- await trio_socket.bind(local_address)
-
- try:
- await trio_socket.connect((host, port))
- except BaseException:
- trio_socket.close()
- raise
-
- return SocketStream(trio_socket)
-
- @classmethod
- async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
- trio_socket = trio.socket.socket(socket.AF_UNIX)
- try:
- await trio_socket.connect(path)
- except BaseException:
- trio_socket.close()
- raise
-
- return UNIXSocketStream(trio_socket)
-
- @classmethod
- def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener:
- return TCPSocketListener(sock)
-
- @classmethod
- def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener:
- return UNIXSocketListener(sock)
-
- @classmethod
- async def create_udp_socket(
- cls,
- family: socket.AddressFamily,
- local_address: IPSockAddrType | None,
- remote_address: IPSockAddrType | None,
- reuse_port: bool,
- ) -> UDPSocket | ConnectedUDPSocket:
- trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM)
-
- if reuse_port:
- trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
-
- if local_address:
- await trio_socket.bind(local_address)
-
- if remote_address:
- await trio_socket.connect(remote_address)
- return ConnectedUDPSocket(trio_socket)
- else:
- return UDPSocket(trio_socket)
-
- @classmethod
- @overload
- async def create_unix_datagram_socket(
- cls, raw_socket: socket.socket, remote_path: None
- ) -> abc.UNIXDatagramSocket: ...
-
- @classmethod
- @overload
- async def create_unix_datagram_socket(
- cls, raw_socket: socket.socket, remote_path: str | bytes
- ) -> abc.ConnectedUNIXDatagramSocket: ...
-
- @classmethod
- async def create_unix_datagram_socket(
- cls, raw_socket: socket.socket, remote_path: str | bytes | None
- ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
- trio_socket = trio.socket.from_stdlib_socket(raw_socket)
-
- if remote_path:
- await trio_socket.connect(remote_path)
- return ConnectedUNIXDatagramSocket(trio_socket)
- else:
- return UNIXDatagramSocket(trio_socket)
-
- @classmethod
- async def getaddrinfo(
- cls,
- host: bytes | str | None,
- port: str | int | None,
- *,
- family: int | AddressFamily = 0,
- type: int | SocketKind = 0,
- proto: int = 0,
- flags: int = 0,
- ) -> Sequence[
- tuple[
- AddressFamily,
- SocketKind,
- int,
- str,
- tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
- ]
- ]:
- return await trio.socket.getaddrinfo(host, port, family, type, proto, flags)
-
- @classmethod
- async def getnameinfo(
- cls, sockaddr: IPSockAddrType, flags: int = 0
- ) -> tuple[str, str]:
- return await trio.socket.getnameinfo(sockaddr, flags)
-
- @classmethod
- async def wait_readable(cls, obj: FileDescriptorLike) -> None:
- try:
- await wait_readable(obj)
- except trio.ClosedResourceError as exc:
- raise ClosedResourceError().with_traceback(exc.__traceback__) from None
- except trio.BusyResourceError:
- raise BusyResourceError("reading from") from None
-
- @classmethod
- async def wait_writable(cls, obj: FileDescriptorLike) -> None:
- try:
- await wait_writable(obj)
- except trio.ClosedResourceError as exc:
- raise ClosedResourceError().with_traceback(exc.__traceback__) from None
- except trio.BusyResourceError:
- raise BusyResourceError("writing to") from None
-
- @classmethod
- def notify_closing(cls, obj: FileDescriptorLike) -> None:
- notify_closing(obj)
-
- @classmethod
- async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener:
- return TCPSocketListener(sock)
-
- @classmethod
- async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return SocketStream(trio_sock)
-
- @classmethod
- async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return UNIXSocketStream(trio_sock)
-
- @classmethod
- async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return UDPSocket(trio_sock)
-
- @classmethod
- async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return ConnectedUDPSocket(trio_sock)
-
- @classmethod
- async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return UNIXDatagramSocket(trio_sock)
-
- @classmethod
- async def wrap_connected_unix_datagram_socket(
- cls, sock: socket.socket
- ) -> ConnectedUNIXDatagramSocket:
- trio_sock = trio.socket.from_stdlib_socket(sock)
- return ConnectedUNIXDatagramSocket(trio_sock)
-
- @classmethod
- def current_default_thread_limiter(cls) -> CapacityLimiter:
- try:
- return _capacity_limiter_wrapper.get()
- except LookupError:
- limiter = CapacityLimiter(
- original=trio.to_thread.current_default_thread_limiter()
- )
- _capacity_limiter_wrapper.set(limiter)
- return limiter
-
- @classmethod
- def open_signal_receiver(
- cls, *signals: Signals
- ) -> AbstractContextManager[AsyncIterator[Signals]]:
- return _SignalReceiver(signals)
-
- @classmethod
- def get_current_task(cls) -> TaskInfo:
- task = current_task()
- return TrioTaskInfo(task)
-
- @classmethod
- def get_running_tasks(cls) -> Sequence[TaskInfo]:
- root_task = current_root_task()
- assert root_task
- task_infos = [TrioTaskInfo(root_task)]
- nurseries = root_task.child_nurseries
- while nurseries:
- new_nurseries: list[trio.Nursery] = []
- for nursery in nurseries:
- for task in nursery.child_tasks:
- task_infos.append(TrioTaskInfo(task))
- new_nurseries.extend(task.child_nurseries)
-
- nurseries = new_nurseries
-
- return task_infos
-
- @classmethod
- async def wait_all_tasks_blocked(cls) -> None:
- from trio.testing import wait_all_tasks_blocked
-
- await wait_all_tasks_blocked()
-
- @classmethod
- def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
- return TestRunner(**options)
-
-
-backend_class = TrioBackend
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__init__.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index ca61144..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc
deleted file mode 100644
index a115135..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc
deleted file mode 100644
index 207f06f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc
deleted file mode 100644
index 371c393..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc
deleted file mode 100644
index a02dcc0..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc
deleted file mode 100644
index 086f7b6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc
deleted file mode 100644
index 00d6eea..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc
deleted file mode 100644
index bd424f2..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc
deleted file mode 100644
index c516e95..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc
deleted file mode 100644
index a823254..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc
deleted file mode 100644
index 6231a32..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc
deleted file mode 100644
index 5f2bb85..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc
deleted file mode 100644
index fdd74a9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc
deleted file mode 100644
index 4bbb724..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc
deleted file mode 100644
index c4608c2..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc
deleted file mode 100644
index 0ed4a9a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py
deleted file mode 100644
index 9f35bae..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py
+++ /dev/null
@@ -1,167 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import socket
-import threading
-from collections.abc import Callable
-from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
-from typing import TYPE_CHECKING, Any
-
-if TYPE_CHECKING:
- from _typeshed import FileDescriptorLike
-
-_selector_lock = threading.Lock()
-_selector: Selector | None = None
-
-
-class Selector:
- def __init__(self) -> None:
- self._thread = threading.Thread(target=self.run, name="AnyIO socket selector")
- self._selector = DefaultSelector()
- self._send, self._receive = socket.socketpair()
- self._send.setblocking(False)
- self._receive.setblocking(False)
- # This somewhat reduces the amount of memory wasted queueing up data
- # for wakeups. With these settings, maximum number of 1-byte sends
- # before getting BlockingIOError:
- # Linux 4.8: 6
- # macOS (darwin 15.5): 1
- # Windows 10: 525347
- # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send
- # blocking, even on non-blocking sockets, so don't do that.)
- self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1)
- self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1)
- # On Windows this is a TCP socket so this might matter. On other
- # platforms this fails b/c AF_UNIX sockets aren't actually TCP.
- try:
- self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- except OSError:
- pass
-
- self._selector.register(self._receive, EVENT_READ)
- self._closed = False
-
- def start(self) -> None:
- self._thread.start()
- threading._register_atexit(self._stop) # type: ignore[attr-defined]
-
- def _stop(self) -> None:
- global _selector
- self._closed = True
- self._notify_self()
- self._send.close()
- self._thread.join()
- self._selector.unregister(self._receive)
- self._receive.close()
- self._selector.close()
- _selector = None
- assert not self._selector.get_map(), (
- "selector still has registered file descriptors after shutdown"
- )
-
- def _notify_self(self) -> None:
- try:
- self._send.send(b"\x00")
- except BlockingIOError:
- pass
-
- def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
- loop = asyncio.get_running_loop()
- try:
- key = self._selector.get_key(fd)
- except KeyError:
- self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)})
- else:
- if EVENT_READ in key.data:
- raise ValueError(
- "this file descriptor is already registered for reading"
- )
-
- key.data[EVENT_READ] = loop, callback
- self._selector.modify(fd, key.events | EVENT_READ, key.data)
-
- self._notify_self()
-
- def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
- loop = asyncio.get_running_loop()
- try:
- key = self._selector.get_key(fd)
- except KeyError:
- self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)})
- else:
- if EVENT_WRITE in key.data:
- raise ValueError(
- "this file descriptor is already registered for writing"
- )
-
- key.data[EVENT_WRITE] = loop, callback
- self._selector.modify(fd, key.events | EVENT_WRITE, key.data)
-
- self._notify_self()
-
- def remove_reader(self, fd: FileDescriptorLike) -> bool:
- try:
- key = self._selector.get_key(fd)
- except KeyError:
- return False
-
- if new_events := key.events ^ EVENT_READ:
- del key.data[EVENT_READ]
- self._selector.modify(fd, new_events, key.data)
- else:
- self._selector.unregister(fd)
-
- return True
-
- def remove_writer(self, fd: FileDescriptorLike) -> bool:
- try:
- key = self._selector.get_key(fd)
- except KeyError:
- return False
-
- if new_events := key.events ^ EVENT_WRITE:
- del key.data[EVENT_WRITE]
- self._selector.modify(fd, new_events, key.data)
- else:
- self._selector.unregister(fd)
-
- return True
-
- def run(self) -> None:
- while not self._closed:
- for key, events in self._selector.select():
- if key.fileobj is self._receive:
- try:
- while self._receive.recv(4096):
- pass
- except BlockingIOError:
- pass
-
- continue
-
- if events & EVENT_READ:
- loop, callback = key.data[EVENT_READ]
- self.remove_reader(key.fd)
- try:
- loop.call_soon_threadsafe(callback)
- except RuntimeError:
- pass # the loop was already closed
-
- if events & EVENT_WRITE:
- loop, callback = key.data[EVENT_WRITE]
- self.remove_writer(key.fd)
- try:
- loop.call_soon_threadsafe(callback)
- except RuntimeError:
- pass # the loop was already closed
-
-
-def get_selector() -> Selector:
- global _selector
-
- with _selector_lock:
- if _selector is None:
- _selector = Selector()
- _selector.start()
-
- return _selector
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py
deleted file mode 100644
index 302f32b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py
+++ /dev/null
@@ -1,200 +0,0 @@
-from __future__ import annotations
-
-from abc import abstractmethod
-from contextlib import AbstractAsyncContextManager, AbstractContextManager
-from inspect import isasyncgen, iscoroutine, isgenerator
-from types import TracebackType
-from typing import Protocol, TypeVar, cast, final
-
-_T_co = TypeVar("_T_co", covariant=True)
-_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None")
-
-
-class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]):
- def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ...
-
-
-class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]):
- def __asynccontextmanager__(
- self,
- ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ...
-
-
-class ContextManagerMixin:
- """
- Mixin class providing context manager functionality via a generator-based
- implementation.
-
- This class allows you to implement a context manager via :meth:`__contextmanager__`
- which should return a generator. The mechanics are meant to mirror those of
- :func:`@contextmanager `.
-
- .. note:: Classes using this mix-in are not reentrant as context managers, meaning
- that once you enter it, you can't re-enter before first exiting it.
-
- .. seealso:: :doc:`contextmanagers`
- """
-
- __cm: AbstractContextManager[object, bool | None] | None = None
-
- @final
- def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co:
- # Needed for mypy to assume self still has the __cm member
- assert isinstance(self, ContextManagerMixin)
- if self.__cm is not None:
- raise RuntimeError(
- f"this {self.__class__.__qualname__} has already been entered"
- )
-
- cm = self.__contextmanager__()
- if not isinstance(cm, AbstractContextManager):
- if isgenerator(cm):
- raise TypeError(
- "__contextmanager__() returned a generator object instead of "
- "a context manager. Did you forget to add the @contextmanager "
- "decorator?"
- )
-
- raise TypeError(
- f"__contextmanager__() did not return a context manager object, "
- f"but {cm.__class__!r}"
- )
-
- if cm is self:
- raise TypeError(
- f"{self.__class__.__qualname__}.__contextmanager__() returned "
- f"self. Did you forget to add the @contextmanager decorator and a "
- f"'yield' statement?"
- )
-
- value = cm.__enter__()
- self.__cm = cm
- return value
-
- @final
- def __exit__(
- self: _SupportsCtxMgr[object, _ExitT_co],
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> _ExitT_co:
- # Needed for mypy to assume self still has the __cm member
- assert isinstance(self, ContextManagerMixin)
- if self.__cm is None:
- raise RuntimeError(
- f"this {self.__class__.__qualname__} has not been entered yet"
- )
-
- # Prevent circular references
- cm = self.__cm
- del self.__cm
-
- return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb))
-
- @abstractmethod
- def __contextmanager__(self) -> AbstractContextManager[object, bool | None]:
- """
- Implement your context manager logic here.
-
- This method **must** be decorated with
- :func:`@contextmanager `.
-
- .. note:: Remember that the ``yield`` will raise any exception raised in the
- enclosed context block, so use a ``finally:`` block to clean up resources!
-
- :return: a context manager object
- """
-
-
-class AsyncContextManagerMixin:
- """
- Mixin class providing async context manager functionality via a generator-based
- implementation.
-
- This class allows you to implement a context manager via
- :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of
- :func:`@asynccontextmanager `.
-
- .. note:: Classes using this mix-in are not reentrant as context managers, meaning
- that once you enter it, you can't re-enter before first exiting it.
-
- .. seealso:: :doc:`contextmanagers`
- """
-
- __cm: AbstractAsyncContextManager[object, bool | None] | None = None
-
- @final
- async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co:
- # Needed for mypy to assume self still has the __cm member
- assert isinstance(self, AsyncContextManagerMixin)
- if self.__cm is not None:
- raise RuntimeError(
- f"this {self.__class__.__qualname__} has already been entered"
- )
-
- cm = self.__asynccontextmanager__()
- if not isinstance(cm, AbstractAsyncContextManager):
- if isasyncgen(cm):
- raise TypeError(
- "__asynccontextmanager__() returned an async generator instead of "
- "an async context manager. Did you forget to add the "
- "@asynccontextmanager decorator?"
- )
- elif iscoroutine(cm):
- cm.close()
- raise TypeError(
- "__asynccontextmanager__() returned a coroutine object instead of "
- "an async context manager. Did you forget to add the "
- "@asynccontextmanager decorator and a 'yield' statement?"
- )
-
- raise TypeError(
- f"__asynccontextmanager__() did not return an async context manager, "
- f"but {cm.__class__!r}"
- )
-
- if cm is self:
- raise TypeError(
- f"{self.__class__.__qualname__}.__asynccontextmanager__() returned "
- f"self. Did you forget to add the @asynccontextmanager decorator and a "
- f"'yield' statement?"
- )
-
- value = await cm.__aenter__()
- self.__cm = cm
- return value
-
- @final
- async def __aexit__(
- self: _SupportsAsyncCtxMgr[object, _ExitT_co],
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> _ExitT_co:
- assert isinstance(self, AsyncContextManagerMixin)
- if self.__cm is None:
- raise RuntimeError(
- f"this {self.__class__.__qualname__} has not been entered yet"
- )
-
- # Prevent circular references
- cm = self.__cm
- del self.__cm
-
- return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb))
-
- @abstractmethod
- def __asynccontextmanager__(
- self,
- ) -> AbstractAsyncContextManager[object, bool | None]:
- """
- Implement your async context manager logic here.
-
- This method **must** be decorated with
- :func:`@asynccontextmanager `.
-
- .. note:: Remember that the ``yield`` will raise any exception raised in the
- enclosed context block, so use a ``finally:`` block to clean up resources!
-
- :return: an async context manager object
- """
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py
deleted file mode 100644
index 59a69cc..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py
+++ /dev/null
@@ -1,234 +0,0 @@
-from __future__ import annotations
-
-import math
-import sys
-import threading
-from collections.abc import Awaitable, Callable, Generator
-from contextlib import contextmanager
-from contextvars import Token
-from importlib import import_module
-from typing import TYPE_CHECKING, Any, TypeVar
-
-from ._exceptions import NoEventLoopError
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-sniffio: Any
-try:
- import sniffio
-except ModuleNotFoundError:
- sniffio = None
-
-if TYPE_CHECKING:
- from ..abc import AsyncBackend
-
-# This must be updated when new backends are introduced
-BACKENDS = "asyncio", "trio"
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-
-threadlocals = threading.local()
-loaded_backends: dict[str, type[AsyncBackend]] = {}
-
-
-def run(
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- *args: Unpack[PosArgsT],
- backend: str = "asyncio",
- backend_options: dict[str, Any] | None = None,
-) -> T_Retval:
- """
- Run the given coroutine function in an asynchronous event loop.
-
- The current thread must not be already running an event loop.
-
- :param func: a coroutine function
- :param args: positional arguments to ``func``
- :param backend: name of the asynchronous event loop implementation – currently
- either ``asyncio`` or ``trio``
- :param backend_options: keyword arguments to call the backend ``run()``
- implementation with (documented :ref:`here `)
- :return: the return value of the coroutine function
- :raises RuntimeError: if an asynchronous event loop is already running in this
- thread
- :raises LookupError: if the named backend is not found
-
- """
- if asynclib_name := current_async_library():
- raise RuntimeError(f"Already running {asynclib_name} in this thread")
-
- try:
- async_backend = get_async_backend(backend)
- except ImportError as exc:
- raise LookupError(f"No such backend: {backend}") from exc
-
- token = None
- if asynclib_name is None:
- # Since we're in control of the event loop, we can cache the name of the async
- # library
- token = set_current_async_library(backend)
-
- try:
- backend_options = backend_options or {}
- return async_backend.run(func, args, {}, backend_options)
- finally:
- reset_current_async_library(token)
-
-
-async def sleep(delay: float) -> None:
- """
- Pause the current task for the specified duration.
-
- :param delay: the duration, in seconds
-
- """
- return await get_async_backend().sleep(delay)
-
-
-async def sleep_forever() -> None:
- """
- Pause the current task until it's cancelled.
-
- This is a shortcut for ``sleep(math.inf)``.
-
- .. versionadded:: 3.1
-
- """
- await sleep(math.inf)
-
-
-async def sleep_until(deadline: float) -> None:
- """
- Pause the current task until the given time.
-
- :param deadline: the absolute time to wake up at (according to the internal
- monotonic clock of the event loop)
-
- .. versionadded:: 3.1
-
- """
- now = current_time()
- await sleep(max(deadline - now, 0))
-
-
-def current_time() -> float:
- """
- Return the current value of the event loop's internal clock.
-
- :return: the clock value (seconds)
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().current_time()
-
-
-def get_all_backends() -> tuple[str, ...]:
- """Return a tuple of the names of all built-in backends."""
- return BACKENDS
-
-
-def get_available_backends() -> tuple[str, ...]:
- """
- Test for the availability of built-in backends.
-
- :return a tuple of the built-in backend names that were successfully imported
-
- .. versionadded:: 4.12
-
- """
- available_backends: list[str] = []
- for backend_name in get_all_backends():
- try:
- get_async_backend(backend_name)
- except ImportError:
- continue
-
- available_backends.append(backend_name)
-
- return tuple(available_backends)
-
-
-def get_cancelled_exc_class() -> type[BaseException]:
- """
- Return the current async library's cancellation exception class.
-
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().cancelled_exception_class()
-
-
-#
-# Private API
-#
-
-
-@contextmanager
-def claim_worker_thread(
- backend_class: type[AsyncBackend], token: object
-) -> Generator[Any, None, None]:
- from ..lowlevel import EventLoopToken
-
- threadlocals.current_token = EventLoopToken(backend_class, token)
- try:
- yield
- finally:
- del threadlocals.current_token
-
-
-def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]:
- if asynclib_name is None:
- asynclib_name = current_async_library()
- if not asynclib_name:
- raise NoEventLoopError(
- f"Not currently running on any asynchronous event loop. "
- f"Available async backends: {', '.join(get_all_backends())}"
- )
-
- # We use our own dict instead of sys.modules to get the already imported back-end
- # class because the appropriate modules in sys.modules could potentially be only
- # partially initialized
- try:
- return loaded_backends[asynclib_name]
- except KeyError:
- module = import_module(f"anyio._backends._{asynclib_name}")
- loaded_backends[asynclib_name] = module.backend_class
- return module.backend_class
-
-
-def current_async_library() -> str | None:
- if sniffio is None:
- # If sniffio is not installed, we assume we're either running asyncio or nothing
- import asyncio
-
- try:
- asyncio.get_running_loop()
- return "asyncio"
- except RuntimeError:
- pass
- else:
- try:
- return sniffio.current_async_library()
- except sniffio.AsyncLibraryNotFoundError:
- pass
-
- return None
-
-
-def set_current_async_library(asynclib_name: str | None) -> Token | None:
- # no-op if sniffio is not installed
- if sniffio is None:
- return None
-
- return sniffio.current_async_library_cvar.set(asynclib_name)
-
-
-def reset_current_async_library(token: Token | None) -> None:
- if token is not None:
- sniffio.current_async_library_cvar.reset(token)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py
deleted file mode 100644
index 3776bed..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py
+++ /dev/null
@@ -1,156 +0,0 @@
-from __future__ import annotations
-
-import sys
-from collections.abc import Generator
-from textwrap import dedent
-from typing import Any
-
-if sys.version_info < (3, 11):
- from exceptiongroup import BaseExceptionGroup
-
-
-class BrokenResourceError(Exception):
- """
- Raised when trying to use a resource that has been rendered unusable due to external
- causes (e.g. a send stream whose peer has disconnected).
- """
-
-
-class BrokenWorkerProcess(Exception):
- """
- Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or
- otherwise misbehaves.
- """
-
-
-class BrokenWorkerInterpreter(Exception):
- """
- Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is
- raised in the subinterpreter.
- """
-
- def __init__(self, excinfo: Any):
- # This was adapted from concurrent.futures.interpreter.ExecutionFailed
- msg = excinfo.formatted
- if not msg:
- if excinfo.type and excinfo.msg:
- msg = f"{excinfo.type.__name__}: {excinfo.msg}"
- else:
- msg = excinfo.type.__name__ or excinfo.msg
-
- super().__init__(msg)
- self.excinfo = excinfo
-
- def __str__(self) -> str:
- try:
- formatted = self.excinfo.errdisplay
- except Exception:
- return super().__str__()
- else:
- return dedent(
- f"""
- {super().__str__()}
-
- Uncaught in the interpreter:
-
- {formatted}
- """.strip()
- )
-
-
-class BusyResourceError(Exception):
- """
- Raised when two tasks are trying to read from or write to the same resource
- concurrently.
- """
-
- def __init__(self, action: str):
- super().__init__(f"Another task is already {action} this resource")
-
-
-class ClosedResourceError(Exception):
- """Raised when trying to use a resource that has been closed."""
-
-
-class ConnectionFailed(OSError):
- """
- Raised when a connection attempt fails.
-
- .. note:: This class inherits from :exc:`OSError` for backwards compatibility.
- """
-
-
-def iterate_exceptions(
- exception: BaseException,
-) -> Generator[BaseException, None, None]:
- if isinstance(exception, BaseExceptionGroup):
- for exc in exception.exceptions:
- yield from iterate_exceptions(exc)
- else:
- yield exception
-
-
-class DelimiterNotFound(Exception):
- """
- Raised during
- :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
- maximum number of bytes has been read without the delimiter being found.
- """
-
- def __init__(self, max_bytes: int) -> None:
- super().__init__(
- f"The delimiter was not found among the first {max_bytes} bytes"
- )
-
-
-class EndOfStream(Exception):
- """
- Raised when trying to read from a stream that has been closed from the other end.
- """
-
-
-class IncompleteRead(Exception):
- """
- Raised during
- :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or
- :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
- connection is closed before the requested amount of bytes has been read.
- """
-
- def __init__(self) -> None:
- super().__init__(
- "The stream was closed before the read operation could be completed"
- )
-
-
-class TypedAttributeLookupError(LookupError):
- """
- Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute
- is not found and no default value has been given.
- """
-
-
-class WouldBlock(Exception):
- """Raised by ``X_nowait`` functions if ``X()`` would block."""
-
-
-class NoEventLoopError(RuntimeError):
- """
- Raised by several functions that require an event loop to be running in the current
- thread when there is no running event loop.
-
- This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync`
- if not calling from an AnyIO worker thread, and no ``token`` was passed.
- """
-
-
-class RunFinishedError(RuntimeError):
- """
- Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event
- loop associated with the explicitly passed token has already finished.
- """
-
- def __init__(self) -> None:
- super().__init__(
- "The event loop associated with the given token has already finished"
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_fileio.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_fileio.py
deleted file mode 100644
index 061f0d7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_fileio.py
+++ /dev/null
@@ -1,797 +0,0 @@
-from __future__ import annotations
-
-import os
-import pathlib
-import sys
-from collections.abc import (
- AsyncIterator,
- Callable,
- Iterable,
- Iterator,
- Sequence,
-)
-from dataclasses import dataclass
-from functools import partial
-from os import PathLike
-from typing import (
- IO,
- TYPE_CHECKING,
- Any,
- AnyStr,
- ClassVar,
- Final,
- Generic,
- overload,
-)
-
-from .. import to_thread
-from ..abc import AsyncResource
-
-if TYPE_CHECKING:
- from types import ModuleType
-
- from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
-else:
- ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
-
-
-class AsyncFile(AsyncResource, Generic[AnyStr]):
- """
- An asynchronous file object.
-
- This class wraps a standard file object and provides async friendly versions of the
- following blocking methods (where available on the original file object):
-
- * read
- * read1
- * readline
- * readlines
- * readinto
- * readinto1
- * write
- * writelines
- * truncate
- * seek
- * tell
- * flush
-
- All other methods are directly passed through.
-
- This class supports the asynchronous context manager protocol which closes the
- underlying file at the end of the context block.
-
- This class also supports asynchronous iteration::
-
- async with await open_file(...) as f:
- async for line in f:
- print(line)
- """
-
- def __init__(self, fp: IO[AnyStr]) -> None:
- self._fp: Any = fp
-
- def __getattr__(self, name: str) -> object:
- return getattr(self._fp, name)
-
- @property
- def wrapped(self) -> IO[AnyStr]:
- """The wrapped file object."""
- return self._fp
-
- async def __aiter__(self) -> AsyncIterator[AnyStr]:
- while True:
- line = await self.readline()
- if line:
- yield line
- else:
- break
-
- async def aclose(self) -> None:
- return await to_thread.run_sync(self._fp.close)
-
- async def read(self, size: int = -1) -> AnyStr:
- return await to_thread.run_sync(self._fp.read, size)
-
- async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
- return await to_thread.run_sync(self._fp.read1, size)
-
- async def readline(self) -> AnyStr:
- return await to_thread.run_sync(self._fp.readline)
-
- async def readlines(self) -> list[AnyStr]:
- return await to_thread.run_sync(self._fp.readlines)
-
- async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
- return await to_thread.run_sync(self._fp.readinto, b)
-
- async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
- return await to_thread.run_sync(self._fp.readinto1, b)
-
- @overload
- async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
-
- @overload
- async def write(self: AsyncFile[str], b: str) -> int: ...
-
- async def write(self, b: ReadableBuffer | str) -> int:
- return await to_thread.run_sync(self._fp.write, b)
-
- @overload
- async def writelines(
- self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
- ) -> None: ...
-
- @overload
- async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
-
- async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
- return await to_thread.run_sync(self._fp.writelines, lines)
-
- async def truncate(self, size: int | None = None) -> int:
- return await to_thread.run_sync(self._fp.truncate, size)
-
- async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
- return await to_thread.run_sync(self._fp.seek, offset, whence)
-
- async def tell(self) -> int:
- return await to_thread.run_sync(self._fp.tell)
-
- async def flush(self) -> None:
- return await to_thread.run_sync(self._fp.flush)
-
-
-@overload
-async def open_file(
- file: str | PathLike[str] | int,
- mode: OpenBinaryMode,
- buffering: int = ...,
- encoding: str | None = ...,
- errors: str | None = ...,
- newline: str | None = ...,
- closefd: bool = ...,
- opener: Callable[[str, int], int] | None = ...,
-) -> AsyncFile[bytes]: ...
-
-
-@overload
-async def open_file(
- file: str | PathLike[str] | int,
- mode: OpenTextMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- errors: str | None = ...,
- newline: str | None = ...,
- closefd: bool = ...,
- opener: Callable[[str, int], int] | None = ...,
-) -> AsyncFile[str]: ...
-
-
-async def open_file(
- file: str | PathLike[str] | int,
- mode: str = "r",
- buffering: int = -1,
- encoding: str | None = None,
- errors: str | None = None,
- newline: str | None = None,
- closefd: bool = True,
- opener: Callable[[str, int], int] | None = None,
-) -> AsyncFile[Any]:
- """
- Open a file asynchronously.
-
- The arguments are exactly the same as for the builtin :func:`open`.
-
- :return: an asynchronous file object
-
- """
- fp = await to_thread.run_sync(
- open, file, mode, buffering, encoding, errors, newline, closefd, opener
- )
- return AsyncFile(fp)
-
-
-def wrap_file(file: IO[AnyStr]) -> AsyncFile[AnyStr]:
- """
- Wrap an existing file as an asynchronous file.
-
- :param file: an existing file-like object
- :return: an asynchronous file object
-
- """
- return AsyncFile(file)
-
-
-@dataclass(eq=False)
-class _PathIterator(AsyncIterator["Path"]):
- iterator: Iterator[PathLike[str]]
-
- async def __anext__(self) -> Path:
- nextval = await to_thread.run_sync(
- next, self.iterator, None, abandon_on_cancel=True
- )
- if nextval is None:
- raise StopAsyncIteration from None
-
- return Path(nextval)
-
-
-class Path:
- """
- An asynchronous version of :class:`pathlib.Path`.
-
- This class cannot be substituted for :class:`pathlib.Path` or
- :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
- interface.
-
- It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
- the deprecated :meth:`~pathlib.Path.link_to` method.
-
- Some methods may be unavailable or have limited functionality, based on the Python
- version:
-
- * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
- * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
- * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
- * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
- * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
- * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
- * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
- available on Python 3.13 or later)
- * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
- * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
- * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
- on Python 3.12 or later)
- * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
-
- Any methods that do disk I/O need to be awaited on. These methods are:
-
- * :meth:`~pathlib.Path.absolute`
- * :meth:`~pathlib.Path.chmod`
- * :meth:`~pathlib.Path.cwd`
- * :meth:`~pathlib.Path.exists`
- * :meth:`~pathlib.Path.expanduser`
- * :meth:`~pathlib.Path.group`
- * :meth:`~pathlib.Path.hardlink_to`
- * :meth:`~pathlib.Path.home`
- * :meth:`~pathlib.Path.is_block_device`
- * :meth:`~pathlib.Path.is_char_device`
- * :meth:`~pathlib.Path.is_dir`
- * :meth:`~pathlib.Path.is_fifo`
- * :meth:`~pathlib.Path.is_file`
- * :meth:`~pathlib.Path.is_junction`
- * :meth:`~pathlib.Path.is_mount`
- * :meth:`~pathlib.Path.is_socket`
- * :meth:`~pathlib.Path.is_symlink`
- * :meth:`~pathlib.Path.lchmod`
- * :meth:`~pathlib.Path.lstat`
- * :meth:`~pathlib.Path.mkdir`
- * :meth:`~pathlib.Path.open`
- * :meth:`~pathlib.Path.owner`
- * :meth:`~pathlib.Path.read_bytes`
- * :meth:`~pathlib.Path.read_text`
- * :meth:`~pathlib.Path.readlink`
- * :meth:`~pathlib.Path.rename`
- * :meth:`~pathlib.Path.replace`
- * :meth:`~pathlib.Path.resolve`
- * :meth:`~pathlib.Path.rmdir`
- * :meth:`~pathlib.Path.samefile`
- * :meth:`~pathlib.Path.stat`
- * :meth:`~pathlib.Path.symlink_to`
- * :meth:`~pathlib.Path.touch`
- * :meth:`~pathlib.Path.unlink`
- * :meth:`~pathlib.Path.walk`
- * :meth:`~pathlib.Path.write_bytes`
- * :meth:`~pathlib.Path.write_text`
-
- Additionally, the following methods return an async iterator yielding
- :class:`~.Path` objects:
-
- * :meth:`~pathlib.Path.glob`
- * :meth:`~pathlib.Path.iterdir`
- * :meth:`~pathlib.Path.rglob`
- """
-
- __slots__ = "_path", "__weakref__"
-
- __weakref__: Any
-
- def __init__(self, *args: str | PathLike[str]) -> None:
- self._path: Final[pathlib.Path] = pathlib.Path(*args)
-
- def __fspath__(self) -> str:
- return self._path.__fspath__()
-
- def __str__(self) -> str:
- return self._path.__str__()
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.as_posix()!r})"
-
- def __bytes__(self) -> bytes:
- return self._path.__bytes__()
-
- def __hash__(self) -> int:
- return self._path.__hash__()
-
- def __eq__(self, other: object) -> bool:
- target = other._path if isinstance(other, Path) else other
- return self._path.__eq__(target)
-
- def __lt__(self, other: pathlib.PurePath | Path) -> bool:
- target = other._path if isinstance(other, Path) else other
- return self._path.__lt__(target)
-
- def __le__(self, other: pathlib.PurePath | Path) -> bool:
- target = other._path if isinstance(other, Path) else other
- return self._path.__le__(target)
-
- def __gt__(self, other: pathlib.PurePath | Path) -> bool:
- target = other._path if isinstance(other, Path) else other
- return self._path.__gt__(target)
-
- def __ge__(self, other: pathlib.PurePath | Path) -> bool:
- target = other._path if isinstance(other, Path) else other
- return self._path.__ge__(target)
-
- def __truediv__(self, other: str | PathLike[str]) -> Path:
- return Path(self._path / other)
-
- def __rtruediv__(self, other: str | PathLike[str]) -> Path:
- return Path(other) / self
-
- @property
- def parts(self) -> tuple[str, ...]:
- return self._path.parts
-
- @property
- def drive(self) -> str:
- return self._path.drive
-
- @property
- def root(self) -> str:
- return self._path.root
-
- @property
- def anchor(self) -> str:
- return self._path.anchor
-
- @property
- def parents(self) -> Sequence[Path]:
- return tuple(Path(p) for p in self._path.parents)
-
- @property
- def parent(self) -> Path:
- return Path(self._path.parent)
-
- @property
- def name(self) -> str:
- return self._path.name
-
- @property
- def suffix(self) -> str:
- return self._path.suffix
-
- @property
- def suffixes(self) -> list[str]:
- return self._path.suffixes
-
- @property
- def stem(self) -> str:
- return self._path.stem
-
- async def absolute(self) -> Path:
- path = await to_thread.run_sync(self._path.absolute)
- return Path(path)
-
- def as_posix(self) -> str:
- return self._path.as_posix()
-
- def as_uri(self) -> str:
- return self._path.as_uri()
-
- if sys.version_info >= (3, 13):
- parser: ClassVar[ModuleType] = pathlib.Path.parser
-
- @classmethod
- def from_uri(cls, uri: str) -> Path:
- return Path(pathlib.Path.from_uri(uri))
-
- def full_match(
- self, path_pattern: str, *, case_sensitive: bool | None = None
- ) -> bool:
- return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
-
- def match(
- self, path_pattern: str, *, case_sensitive: bool | None = None
- ) -> bool:
- return self._path.match(path_pattern, case_sensitive=case_sensitive)
- else:
-
- def match(self, path_pattern: str) -> bool:
- return self._path.match(path_pattern)
-
- if sys.version_info >= (3, 14):
-
- @property
- def info(self) -> Any: # TODO: add return type annotation when Typeshed gets it
- return self._path.info
-
- async def copy(
- self,
- target: str | os.PathLike[str],
- *,
- follow_symlinks: bool = True,
- preserve_metadata: bool = False,
- ) -> Path:
- func = partial(
- self._path.copy,
- follow_symlinks=follow_symlinks,
- preserve_metadata=preserve_metadata,
- )
- return Path(await to_thread.run_sync(func, pathlib.Path(target)))
-
- async def copy_into(
- self,
- target_dir: str | os.PathLike[str],
- *,
- follow_symlinks: bool = True,
- preserve_metadata: bool = False,
- ) -> Path:
- func = partial(
- self._path.copy_into,
- follow_symlinks=follow_symlinks,
- preserve_metadata=preserve_metadata,
- )
- return Path(await to_thread.run_sync(func, pathlib.Path(target_dir)))
-
- async def move(self, target: str | os.PathLike[str]) -> Path:
- # Upstream does not handle anyio.Path properly as a PathLike
- target = pathlib.Path(target)
- return Path(await to_thread.run_sync(self._path.move, target))
-
- async def move_into(
- self,
- target_dir: str | os.PathLike[str],
- ) -> Path:
- return Path(await to_thread.run_sync(self._path.move_into, target_dir))
-
- def is_relative_to(self, other: str | PathLike[str]) -> bool:
- try:
- self.relative_to(other)
- return True
- except ValueError:
- return False
-
- async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
- func = partial(os.chmod, follow_symlinks=follow_symlinks)
- return await to_thread.run_sync(func, self._path, mode)
-
- @classmethod
- async def cwd(cls) -> Path:
- path = await to_thread.run_sync(pathlib.Path.cwd)
- return cls(path)
-
- async def exists(self) -> bool:
- return await to_thread.run_sync(self._path.exists, abandon_on_cancel=True)
-
- async def expanduser(self) -> Path:
- return Path(
- await to_thread.run_sync(self._path.expanduser, abandon_on_cancel=True)
- )
-
- if sys.version_info < (3, 12):
- # Python 3.11 and earlier
- def glob(self, pattern: str) -> AsyncIterator[Path]:
- gen = self._path.glob(pattern)
- return _PathIterator(gen)
- elif (3, 12) <= sys.version_info < (3, 13):
- # changed in Python 3.12:
- # - The case_sensitive parameter was added.
- def glob(
- self,
- pattern: str,
- *,
- case_sensitive: bool | None = None,
- ) -> AsyncIterator[Path]:
- gen = self._path.glob(pattern, case_sensitive=case_sensitive)
- return _PathIterator(gen)
- elif sys.version_info >= (3, 13):
- # Changed in Python 3.13:
- # - The recurse_symlinks parameter was added.
- # - The pattern parameter accepts a path-like object.
- def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
- self,
- pattern: str | PathLike[str],
- *,
- case_sensitive: bool | None = None,
- recurse_symlinks: bool = False,
- ) -> AsyncIterator[Path]:
- gen = self._path.glob(
- pattern, # type: ignore[arg-type]
- case_sensitive=case_sensitive,
- recurse_symlinks=recurse_symlinks,
- )
- return _PathIterator(gen)
-
- async def group(self) -> str:
- return await to_thread.run_sync(self._path.group, abandon_on_cancel=True)
-
- async def hardlink_to(
- self, target: str | bytes | PathLike[str] | PathLike[bytes]
- ) -> None:
- if isinstance(target, Path):
- target = target._path
-
- await to_thread.run_sync(os.link, target, self)
-
- @classmethod
- async def home(cls) -> Path:
- home_path = await to_thread.run_sync(pathlib.Path.home)
- return cls(home_path)
-
- def is_absolute(self) -> bool:
- return self._path.is_absolute()
-
- async def is_block_device(self) -> bool:
- return await to_thread.run_sync(
- self._path.is_block_device, abandon_on_cancel=True
- )
-
- async def is_char_device(self) -> bool:
- return await to_thread.run_sync(
- self._path.is_char_device, abandon_on_cancel=True
- )
-
- async def is_dir(self) -> bool:
- return await to_thread.run_sync(self._path.is_dir, abandon_on_cancel=True)
-
- async def is_fifo(self) -> bool:
- return await to_thread.run_sync(self._path.is_fifo, abandon_on_cancel=True)
-
- async def is_file(self) -> bool:
- return await to_thread.run_sync(self._path.is_file, abandon_on_cancel=True)
-
- if sys.version_info >= (3, 12):
-
- async def is_junction(self) -> bool:
- return await to_thread.run_sync(self._path.is_junction)
-
- async def is_mount(self) -> bool:
- return await to_thread.run_sync(
- os.path.ismount, self._path, abandon_on_cancel=True
- )
-
- def is_reserved(self) -> bool:
- return self._path.is_reserved()
-
- async def is_socket(self) -> bool:
- return await to_thread.run_sync(self._path.is_socket, abandon_on_cancel=True)
-
- async def is_symlink(self) -> bool:
- return await to_thread.run_sync(self._path.is_symlink, abandon_on_cancel=True)
-
- async def iterdir(self) -> AsyncIterator[Path]:
- gen = (
- self._path.iterdir()
- if sys.version_info < (3, 13)
- else await to_thread.run_sync(self._path.iterdir, abandon_on_cancel=True)
- )
- async for path in _PathIterator(gen):
- yield path
-
- def joinpath(self, *args: str | PathLike[str]) -> Path:
- return Path(self._path.joinpath(*args))
-
- async def lchmod(self, mode: int) -> None:
- await to_thread.run_sync(self._path.lchmod, mode)
-
- async def lstat(self) -> os.stat_result:
- return await to_thread.run_sync(self._path.lstat, abandon_on_cancel=True)
-
- async def mkdir(
- self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
- ) -> None:
- await to_thread.run_sync(self._path.mkdir, mode, parents, exist_ok)
-
- @overload
- async def open(
- self,
- mode: OpenBinaryMode,
- buffering: int = ...,
- encoding: str | None = ...,
- errors: str | None = ...,
- newline: str | None = ...,
- ) -> AsyncFile[bytes]: ...
-
- @overload
- async def open(
- self,
- mode: OpenTextMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- errors: str | None = ...,
- newline: str | None = ...,
- ) -> AsyncFile[str]: ...
-
- async def open(
- self,
- mode: str = "r",
- buffering: int = -1,
- encoding: str | None = None,
- errors: str | None = None,
- newline: str | None = None,
- ) -> AsyncFile[Any]:
- fp = await to_thread.run_sync(
- self._path.open, mode, buffering, encoding, errors, newline
- )
- return AsyncFile(fp)
-
- async def owner(self) -> str:
- return await to_thread.run_sync(self._path.owner, abandon_on_cancel=True)
-
- async def read_bytes(self) -> bytes:
- return await to_thread.run_sync(self._path.read_bytes)
-
- async def read_text(
- self, encoding: str | None = None, errors: str | None = None
- ) -> str:
- return await to_thread.run_sync(self._path.read_text, encoding, errors)
-
- if sys.version_info >= (3, 12):
-
- def relative_to(
- self, *other: str | PathLike[str], walk_up: bool = False
- ) -> Path:
- # relative_to() should work with any PathLike but it doesn't
- others = [pathlib.Path(other) for other in other]
- return Path(self._path.relative_to(*others, walk_up=walk_up))
-
- else:
-
- def relative_to(self, *other: str | PathLike[str]) -> Path:
- return Path(self._path.relative_to(*other))
-
- async def readlink(self) -> Path:
- target = await to_thread.run_sync(os.readlink, self._path)
- return Path(target)
-
- async def rename(self, target: str | pathlib.PurePath | Path) -> Path:
- if isinstance(target, Path):
- target = target._path
-
- await to_thread.run_sync(self._path.rename, target)
- return Path(target)
-
- async def replace(self, target: str | pathlib.PurePath | Path) -> Path:
- if isinstance(target, Path):
- target = target._path
-
- await to_thread.run_sync(self._path.replace, target)
- return Path(target)
-
- async def resolve(self, strict: bool = False) -> Path:
- func = partial(self._path.resolve, strict=strict)
- return Path(await to_thread.run_sync(func, abandon_on_cancel=True))
-
- if sys.version_info < (3, 12):
- # Pre Python 3.12
- def rglob(self, pattern: str) -> AsyncIterator[Path]:
- gen = self._path.rglob(pattern)
- return _PathIterator(gen)
- elif (3, 12) <= sys.version_info < (3, 13):
- # Changed in Python 3.12:
- # - The case_sensitive parameter was added.
- def rglob(
- self, pattern: str, *, case_sensitive: bool | None = None
- ) -> AsyncIterator[Path]:
- gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
- return _PathIterator(gen)
- elif sys.version_info >= (3, 13):
- # Changed in Python 3.13:
- # - The recurse_symlinks parameter was added.
- # - The pattern parameter accepts a path-like object.
- def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
- self,
- pattern: str | PathLike[str],
- *,
- case_sensitive: bool | None = None,
- recurse_symlinks: bool = False,
- ) -> AsyncIterator[Path]:
- gen = self._path.rglob(
- pattern, # type: ignore[arg-type]
- case_sensitive=case_sensitive,
- recurse_symlinks=recurse_symlinks,
- )
- return _PathIterator(gen)
-
- async def rmdir(self) -> None:
- await to_thread.run_sync(self._path.rmdir)
-
- async def samefile(self, other_path: str | PathLike[str]) -> bool:
- if isinstance(other_path, Path):
- other_path = other_path._path
-
- return await to_thread.run_sync(
- self._path.samefile, other_path, abandon_on_cancel=True
- )
-
- async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
- func = partial(os.stat, follow_symlinks=follow_symlinks)
- return await to_thread.run_sync(func, self._path, abandon_on_cancel=True)
-
- async def symlink_to(
- self,
- target: str | bytes | PathLike[str] | PathLike[bytes],
- target_is_directory: bool = False,
- ) -> None:
- if isinstance(target, Path):
- target = target._path
-
- await to_thread.run_sync(self._path.symlink_to, target, target_is_directory)
-
- async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
- await to_thread.run_sync(self._path.touch, mode, exist_ok)
-
- async def unlink(self, missing_ok: bool = False) -> None:
- try:
- await to_thread.run_sync(self._path.unlink)
- except FileNotFoundError:
- if not missing_ok:
- raise
-
- if sys.version_info >= (3, 12):
-
- async def walk(
- self,
- top_down: bool = True,
- on_error: Callable[[OSError], object] | None = None,
- follow_symlinks: bool = False,
- ) -> AsyncIterator[tuple[Path, list[str], list[str]]]:
- def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
- try:
- return next(gen)
- except StopIteration:
- return None
-
- gen = self._path.walk(top_down, on_error, follow_symlinks)
- while True:
- value = await to_thread.run_sync(get_next_value)
- if value is None:
- return
-
- root, dirs, paths = value
- yield Path(root), dirs, paths
-
- def with_name(self, name: str) -> Path:
- return Path(self._path.with_name(name))
-
- def with_stem(self, stem: str) -> Path:
- return Path(self._path.with_name(stem + self._path.suffix))
-
- def with_suffix(self, suffix: str) -> Path:
- return Path(self._path.with_suffix(suffix))
-
- def with_segments(self, *pathsegments: str | PathLike[str]) -> Path:
- return Path(*pathsegments)
-
- async def write_bytes(self, data: bytes) -> int:
- return await to_thread.run_sync(self._path.write_bytes, data)
-
- async def write_text(
- self,
- data: str,
- encoding: str | None = None,
- errors: str | None = None,
- newline: str | None = None,
- ) -> int:
- # Path.write_text() does not support the "newline" parameter before Python 3.10
- def sync_write_text() -> int:
- with self._path.open(
- "w", encoding=encoding, errors=errors, newline=newline
- ) as fp:
- return fp.write(data)
-
- return await to_thread.run_sync(sync_write_text)
-
-
-PathLike.register(Path)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_resources.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_resources.py
deleted file mode 100644
index b9a5344..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_resources.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from __future__ import annotations
-
-from ..abc import AsyncResource
-from ._tasks import CancelScope
-
-
-async def aclose_forcefully(resource: AsyncResource) -> None:
- """
- Close an asynchronous resource in a cancelled scope.
-
- Doing this closes the resource without waiting on anything.
-
- :param resource: the resource to close
-
- """
- with CancelScope() as scope:
- scope.cancel()
- await resource.aclose()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_signals.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_signals.py
deleted file mode 100644
index e24c79e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_signals.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import AsyncIterator
-from contextlib import AbstractContextManager
-from signal import Signals
-
-from ._eventloop import get_async_backend
-
-
-def open_signal_receiver(
- *signals: Signals,
-) -> AbstractContextManager[AsyncIterator[Signals]]:
- """
- Start receiving operating system signals.
-
- :param signals: signals to receive (e.g. ``signal.SIGINT``)
- :return: an asynchronous context manager for an asynchronous iterator which yields
- signal numbers
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- .. warning:: Windows does not support signals natively so it is best to avoid
- relying on this in cross-platform applications.
-
- .. warning:: On asyncio, this permanently replaces any previous signal handler for
- the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`.
-
- """
- return get_async_backend().open_signal_receiver(*signals)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_sockets.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_sockets.py
deleted file mode 100644
index 6c99b3a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_sockets.py
+++ /dev/null
@@ -1,1003 +0,0 @@
-from __future__ import annotations
-
-import errno
-import os
-import socket
-import ssl
-import stat
-import sys
-from collections.abc import Awaitable
-from dataclasses import dataclass
-from ipaddress import IPv4Address, IPv6Address, ip_address
-from os import PathLike, chmod
-from socket import AddressFamily, SocketKind
-from typing import TYPE_CHECKING, Any, Literal, cast, overload
-
-from .. import ConnectionFailed, to_thread
-from ..abc import (
- ByteStreamConnectable,
- ConnectedUDPSocket,
- ConnectedUNIXDatagramSocket,
- IPAddressType,
- IPSockAddrType,
- SocketListener,
- SocketStream,
- UDPSocket,
- UNIXDatagramSocket,
- UNIXSocketStream,
-)
-from ..streams.stapled import MultiListener
-from ..streams.tls import TLSConnectable, TLSStream
-from ._eventloop import get_async_backend
-from ._resources import aclose_forcefully
-from ._synchronization import Event
-from ._tasks import create_task_group, move_on_after
-
-if TYPE_CHECKING:
- from _typeshed import FileDescriptorLike
-else:
- FileDescriptorLike = object
-
-if sys.version_info < (3, 11):
- from exceptiongroup import ExceptionGroup
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
-if sys.version_info < (3, 13):
- from typing_extensions import deprecated
-else:
- from warnings import deprecated
-
-IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515
-
-AnyIPAddressFamily = Literal[
- AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6
-]
-IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6]
-
-
-# tls_hostname given
-@overload
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = ...,
- ssl_context: ssl.SSLContext | None = ...,
- tls_standard_compatible: bool = ...,
- tls_hostname: str,
- happy_eyeballs_delay: float = ...,
-) -> TLSStream: ...
-
-
-# ssl_context given
-@overload
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = ...,
- ssl_context: ssl.SSLContext,
- tls_standard_compatible: bool = ...,
- tls_hostname: str | None = ...,
- happy_eyeballs_delay: float = ...,
-) -> TLSStream: ...
-
-
-# tls=True
-@overload
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = ...,
- tls: Literal[True],
- ssl_context: ssl.SSLContext | None = ...,
- tls_standard_compatible: bool = ...,
- tls_hostname: str | None = ...,
- happy_eyeballs_delay: float = ...,
-) -> TLSStream: ...
-
-
-# tls=False
-@overload
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = ...,
- tls: Literal[False],
- ssl_context: ssl.SSLContext | None = ...,
- tls_standard_compatible: bool = ...,
- tls_hostname: str | None = ...,
- happy_eyeballs_delay: float = ...,
-) -> SocketStream: ...
-
-
-# No TLS arguments
-@overload
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = ...,
- happy_eyeballs_delay: float = ...,
-) -> SocketStream: ...
-
-
-async def connect_tcp(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- local_host: IPAddressType | None = None,
- tls: bool = False,
- ssl_context: ssl.SSLContext | None = None,
- tls_standard_compatible: bool = True,
- tls_hostname: str | None = None,
- happy_eyeballs_delay: float = 0.25,
-) -> SocketStream | TLSStream:
- """
- Connect to a host using the TCP protocol.
-
- This function implements the stateless version of the Happy Eyeballs algorithm (RFC
- 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses,
- each one is tried until one connection attempt succeeds. If the first attempt does
- not connected within 250 milliseconds, a second attempt is started using the next
- address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if
- available) is tried first.
-
- When the connection has been established, a TLS handshake will be done if either
- ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``.
-
- :param remote_host: the IP address or host name to connect to
- :param remote_port: port on the target host to connect to
- :param local_host: the interface address or name to bind the socket to before
- connecting
- :param tls: ``True`` to do a TLS handshake with the connected stream and return a
- :class:`~anyio.streams.tls.TLSStream` instead
- :param ssl_context: the SSL context object to use (if omitted, a default context is
- created)
- :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake
- before closing the stream and requires that the server does this as well.
- Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream.
- Some protocols, such as HTTP, require this option to be ``False``.
- See :meth:`~ssl.SSLContext.wrap_socket` for details.
- :param tls_hostname: host name to check the server certificate against (defaults to
- the value of ``remote_host``)
- :param happy_eyeballs_delay: delay (in seconds) before starting the next connection
- attempt
- :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream
- :raises ConnectionFailed: if the connection fails
-
- """
- # Placed here due to https://github.com/python/mypy/issues/7057
- connected_stream: SocketStream | None = None
-
- async def try_connect(remote_host: str, event: Event) -> None:
- nonlocal connected_stream
- try:
- stream = await asynclib.connect_tcp(remote_host, remote_port, local_address)
- except OSError as exc:
- oserrors.append(exc)
- return
- else:
- if connected_stream is None:
- connected_stream = stream
- tg.cancel_scope.cancel()
- else:
- await stream.aclose()
- finally:
- event.set()
-
- asynclib = get_async_backend()
- local_address: IPSockAddrType | None = None
- family = socket.AF_UNSPEC
- if local_host:
- gai_res = await getaddrinfo(str(local_host), None)
- family, *_, local_address = gai_res[0]
-
- target_host = str(remote_host)
- try:
- addr_obj = ip_address(remote_host)
- except ValueError:
- addr_obj = None
-
- if addr_obj is not None:
- if isinstance(addr_obj, IPv6Address):
- target_addrs = [(socket.AF_INET6, addr_obj.compressed)]
- else:
- target_addrs = [(socket.AF_INET, addr_obj.compressed)]
- else:
- # getaddrinfo() will raise an exception if name resolution fails
- gai_res = await getaddrinfo(
- target_host, remote_port, family=family, type=socket.SOCK_STREAM
- )
-
- # Organize the list so that the first address is an IPv6 address (if available)
- # and the second one is an IPv4 addresses. The rest can be in whatever order.
- v6_found = v4_found = False
- target_addrs = []
- for af, *_, sa in gai_res:
- if af == socket.AF_INET6 and not v6_found:
- v6_found = True
- target_addrs.insert(0, (af, sa[0]))
- elif af == socket.AF_INET and not v4_found and v6_found:
- v4_found = True
- target_addrs.insert(1, (af, sa[0]))
- else:
- target_addrs.append((af, sa[0]))
-
- oserrors: list[OSError] = []
- try:
- async with create_task_group() as tg:
- for _af, addr in target_addrs:
- event = Event()
- tg.start_soon(try_connect, addr, event)
- with move_on_after(happy_eyeballs_delay):
- await event.wait()
-
- if connected_stream is None:
- cause = (
- oserrors[0]
- if len(oserrors) == 1
- else ExceptionGroup("multiple connection attempts failed", oserrors)
- )
- raise OSError("All connection attempts failed") from cause
- finally:
- oserrors.clear()
-
- if tls or tls_hostname or ssl_context:
- try:
- return await TLSStream.wrap(
- connected_stream,
- server_side=False,
- hostname=tls_hostname or str(remote_host),
- ssl_context=ssl_context,
- standard_compatible=tls_standard_compatible,
- )
- except BaseException:
- await aclose_forcefully(connected_stream)
- raise
-
- return connected_stream
-
-
-async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream:
- """
- Connect to the given UNIX socket.
-
- Not available on Windows.
-
- :param path: path to the socket
- :return: a socket stream object
- :raises ConnectionFailed: if the connection fails
-
- """
- path = os.fspath(path)
- return await get_async_backend().connect_unix(path)
-
-
-async def create_tcp_listener(
- *,
- local_host: IPAddressType | None = None,
- local_port: int = 0,
- family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC,
- backlog: int = 65536,
- reuse_port: bool = False,
-) -> MultiListener[SocketStream]:
- """
- Create a TCP socket listener.
-
- :param local_port: port number to listen on
- :param local_host: IP address of the interface to listen on. If omitted, listen on
- all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address
- family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6.
- :param family: address family (used if ``local_host`` was omitted)
- :param backlog: maximum number of queued incoming connections (up to a maximum of
- 2**16, or 65536)
- :param reuse_port: ``True`` to allow multiple sockets to bind to the same
- address/port (not supported on Windows)
- :return: a multi-listener object containing one or more socket listeners
- :raises OSError: if there's an error creating a socket, or binding to one or more
- interfaces failed
-
- """
- asynclib = get_async_backend()
- backlog = min(backlog, 65536)
- local_host = str(local_host) if local_host is not None else None
-
- def setup_raw_socket(
- fam: AddressFamily,
- bind_addr: tuple[str, int] | tuple[str, int, int, int],
- *,
- v6only: bool = True,
- ) -> socket.socket:
- sock = socket.socket(fam)
- try:
- sock.setblocking(False)
-
- if fam == AddressFamily.AF_INET6:
- sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only)
-
- # For Windows, enable exclusive address use. For others, enable address
- # reuse.
- if sys.platform == "win32":
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
- else:
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
- if reuse_port:
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
-
- # Workaround for #554
- if fam == socket.AF_INET6 and "%" in bind_addr[0]:
- addr, scope_id = bind_addr[0].split("%", 1)
- bind_addr = (addr, bind_addr[1], 0, int(scope_id))
-
- sock.bind(bind_addr)
- sock.listen(backlog)
- except BaseException:
- sock.close()
- raise
-
- return sock
-
- # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug
- # where we don't get the correct scope ID for IPv6 link-local addresses when passing
- # type=socket.SOCK_STREAM to getaddrinfo():
- # https://github.com/MagicStack/uvloop/issues/539
- gai_res = await getaddrinfo(
- local_host,
- local_port,
- family=family,
- type=socket.SOCK_STREAM if sys.platform == "win32" else 0,
- flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
- )
-
- # The set comprehension is here to work around a glibc bug:
- # https://sourceware.org/bugzilla/show_bug.cgi?id=14969
- sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM})
-
- # Special case for dual-stack binding on the "any" interface
- if (
- local_host is None
- and family == AddressFamily.AF_UNSPEC
- and socket.has_dualstack_ipv6()
- and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res)
- ):
- raw_socket = setup_raw_socket(
- AddressFamily.AF_INET6, ("::", local_port), v6only=False
- )
- listener = asynclib.create_tcp_listener(raw_socket)
- return MultiListener([listener])
-
- errors: list[OSError] = []
- try:
- for _ in range(len(sockaddrs)):
- listeners: list[SocketListener] = []
- bound_ephemeral_port = local_port
- try:
- for fam, *_, sockaddr in sockaddrs:
- sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:]
- raw_socket = setup_raw_socket(fam, sockaddr)
-
- # Store the assigned port if an ephemeral port was requested, so
- # we'll bind to the same port on all interfaces
- if local_port == 0 and len(gai_res) > 1:
- bound_ephemeral_port = raw_socket.getsockname()[1]
-
- listeners.append(asynclib.create_tcp_listener(raw_socket))
- except BaseException as exc:
- for listener in listeners:
- await listener.aclose()
-
- # If an ephemeral port was requested but binding the assigned port
- # failed for another interface, rotate the address list and try again
- if (
- isinstance(exc, OSError)
- and exc.errno == errno.EADDRINUSE
- and local_port == 0
- and bound_ephemeral_port
- ):
- errors.append(exc)
- sockaddrs.append(sockaddrs.pop(0))
- continue
-
- raise
-
- return MultiListener(listeners)
-
- raise OSError(
- f"Could not create {len(sockaddrs)} listeners with a consistent port"
- ) from ExceptionGroup("Several bind attempts failed", errors)
- finally:
- del errors # Prevent reference cycles
-
-
-async def create_unix_listener(
- path: str | bytes | PathLike[Any],
- *,
- mode: int | None = None,
- backlog: int = 65536,
-) -> SocketListener:
- """
- Create a UNIX socket listener.
-
- Not available on Windows.
-
- :param path: path of the socket
- :param mode: permissions to set on the socket
- :param backlog: maximum number of queued incoming connections (up to a maximum of
- 2**16, or 65536)
- :return: a listener object
-
- .. versionchanged:: 3.0
- If a socket already exists on the file system in the given path, it will be
- removed first.
-
- """
- backlog = min(backlog, 65536)
- raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM)
- try:
- raw_socket.listen(backlog)
- return get_async_backend().create_unix_listener(raw_socket)
- except BaseException:
- raw_socket.close()
- raise
-
-
-async def create_udp_socket(
- family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
- *,
- local_host: IPAddressType | None = None,
- local_port: int = 0,
- reuse_port: bool = False,
-) -> UDPSocket:
- """
- Create a UDP socket.
-
- If ``port`` has been given, the socket will be bound to this port on the local
- machine, making this socket suitable for providing UDP based services.
-
- :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
- determined from ``local_host`` if omitted
- :param local_host: IP address or host name of the local interface to bind to
- :param local_port: local port to bind to
- :param reuse_port: ``True`` to allow multiple sockets to bind to the same
- address/port (not supported on Windows)
- :return: a UDP socket
-
- """
- if family is AddressFamily.AF_UNSPEC and not local_host:
- raise ValueError('Either "family" or "local_host" must be given')
-
- if local_host:
- gai_res = await getaddrinfo(
- str(local_host),
- local_port,
- family=family,
- type=socket.SOCK_DGRAM,
- flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
- )
- family = cast(AnyIPAddressFamily, gai_res[0][0])
- local_address = gai_res[0][-1]
- elif family is AddressFamily.AF_INET6:
- local_address = ("::", 0)
- else:
- local_address = ("0.0.0.0", 0)
-
- sock = await get_async_backend().create_udp_socket(
- family, local_address, None, reuse_port
- )
- return cast(UDPSocket, sock)
-
-
-async def create_connected_udp_socket(
- remote_host: IPAddressType,
- remote_port: int,
- *,
- family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
- local_host: IPAddressType | None = None,
- local_port: int = 0,
- reuse_port: bool = False,
-) -> ConnectedUDPSocket:
- """
- Create a connected UDP socket.
-
- Connected UDP sockets can only communicate with the specified remote host/port, an
- any packets sent from other sources are dropped.
-
- :param remote_host: remote host to set as the default target
- :param remote_port: port on the remote host to set as the default target
- :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
- determined from ``local_host`` or ``remote_host`` if omitted
- :param local_host: IP address or host name of the local interface to bind to
- :param local_port: local port to bind to
- :param reuse_port: ``True`` to allow multiple sockets to bind to the same
- address/port (not supported on Windows)
- :return: a connected UDP socket
-
- """
- local_address = None
- if local_host:
- gai_res = await getaddrinfo(
- str(local_host),
- local_port,
- family=family,
- type=socket.SOCK_DGRAM,
- flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
- )
- family = cast(AnyIPAddressFamily, gai_res[0][0])
- local_address = gai_res[0][-1]
-
- gai_res = await getaddrinfo(
- str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM
- )
- family = cast(AnyIPAddressFamily, gai_res[0][0])
- remote_address = gai_res[0][-1]
-
- sock = await get_async_backend().create_udp_socket(
- family, local_address, remote_address, reuse_port
- )
- return cast(ConnectedUDPSocket, sock)
-
-
-async def create_unix_datagram_socket(
- *,
- local_path: None | str | bytes | PathLike[Any] = None,
- local_mode: int | None = None,
-) -> UNIXDatagramSocket:
- """
- Create a UNIX datagram socket.
-
- Not available on Windows.
-
- If ``local_path`` has been given, the socket will be bound to this path, making this
- socket suitable for receiving datagrams from other processes. Other processes can
- send datagrams to this socket only if ``local_path`` is set.
-
- If a socket already exists on the file system in the ``local_path``, it will be
- removed first.
-
- :param local_path: the path on which to bind to
- :param local_mode: permissions to set on the local socket
- :return: a UNIX datagram socket
-
- """
- raw_socket = await setup_unix_local_socket(
- local_path, local_mode, socket.SOCK_DGRAM
- )
- return await get_async_backend().create_unix_datagram_socket(raw_socket, None)
-
-
-async def create_connected_unix_datagram_socket(
- remote_path: str | bytes | PathLike[Any],
- *,
- local_path: None | str | bytes | PathLike[Any] = None,
- local_mode: int | None = None,
-) -> ConnectedUNIXDatagramSocket:
- """
- Create a connected UNIX datagram socket.
-
- Connected datagram sockets can only communicate with the specified remote path.
-
- If ``local_path`` has been given, the socket will be bound to this path, making
- this socket suitable for receiving datagrams from other processes. Other processes
- can send datagrams to this socket only if ``local_path`` is set.
-
- If a socket already exists on the file system in the ``local_path``, it will be
- removed first.
-
- :param remote_path: the path to set as the default target
- :param local_path: the path on which to bind to
- :param local_mode: permissions to set on the local socket
- :return: a connected UNIX datagram socket
-
- """
- remote_path = os.fspath(remote_path)
- raw_socket = await setup_unix_local_socket(
- local_path, local_mode, socket.SOCK_DGRAM
- )
- return await get_async_backend().create_unix_datagram_socket(
- raw_socket, remote_path
- )
-
-
-async def getaddrinfo(
- host: bytes | str | None,
- port: str | int | None,
- *,
- family: int | AddressFamily = 0,
- type: int | SocketKind = 0,
- proto: int = 0,
- flags: int = 0,
-) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]:
- """
- Look up a numeric IP address given a host name.
-
- Internationalized domain names are translated according to the (non-transitional)
- IDNA 2008 standard.
-
- .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of
- (host, port), unlike what :func:`socket.getaddrinfo` does.
-
- :param host: host name
- :param port: port number
- :param family: socket family (`'AF_INET``, ...)
- :param type: socket type (``SOCK_STREAM``, ...)
- :param proto: protocol number
- :param flags: flags to pass to upstream ``getaddrinfo()``
- :return: list of tuples containing (family, type, proto, canonname, sockaddr)
-
- .. seealso:: :func:`socket.getaddrinfo`
-
- """
- # Handle unicode hostnames
- if isinstance(host, str):
- try:
- encoded_host: bytes | None = host.encode("ascii")
- except UnicodeEncodeError:
- import idna
-
- encoded_host = idna.encode(host, uts46=True)
- else:
- encoded_host = host
-
- gai_res = await get_async_backend().getaddrinfo(
- encoded_host, port, family=family, type=type, proto=proto, flags=flags
- )
- return [
- (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr))
- for family, type, proto, canonname, sockaddr in gai_res
- # filter out IPv6 results when IPv6 is disabled
- if not isinstance(sockaddr[0], int)
- ]
-
-
-def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]:
- """
- Look up the host name of an IP address.
-
- :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4)
- :param flags: flags to pass to upstream ``getnameinfo()``
- :return: a tuple of (host name, service name)
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- .. seealso:: :func:`socket.getnameinfo`
-
- """
- return get_async_backend().getnameinfo(sockaddr, flags)
-
-
-@deprecated("This function is deprecated; use `wait_readable` instead")
-def wait_socket_readable(sock: socket.socket) -> Awaitable[None]:
- """
- .. deprecated:: 4.7.0
- Use :func:`wait_readable` instead.
-
- Wait until the given socket has data to be read.
-
- .. warning:: Only use this on raw sockets that have not been wrapped by any higher
- level constructs like socket streams!
-
- :param sock: a socket object
- :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
- socket to become readable
- :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
- to become readable
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().wait_readable(sock.fileno())
-
-
-@deprecated("This function is deprecated; use `wait_writable` instead")
-def wait_socket_writable(sock: socket.socket) -> Awaitable[None]:
- """
- .. deprecated:: 4.7.0
- Use :func:`wait_writable` instead.
-
- Wait until the given socket can be written to.
-
- This does **NOT** work on Windows when using the asyncio backend with a proactor
- event loop (default on py3.8+).
-
- .. warning:: Only use this on raw sockets that have not been wrapped by any higher
- level constructs like socket streams!
-
- :param sock: a socket object
- :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
- socket to become writable
- :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
- to become writable
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().wait_writable(sock.fileno())
-
-
-def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]:
- """
- Wait until the given object has data to be read.
-
- On Unix systems, ``obj`` must either be an integer file descriptor, or else an
- object with a ``.fileno()`` method which returns an integer file descriptor. Any
- kind of file descriptor can be passed, though the exact semantics will depend on
- your kernel. For example, this probably won't do anything useful for on-disk files.
-
- On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an
- object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File
- descriptors aren't supported, and neither are handles that refer to anything besides
- a ``SOCKET``.
-
- On backends where this functionality is not natively provided (asyncio
- ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread
- which is set to shut down when the interpreter shuts down.
-
- .. warning:: Don't use this on raw sockets that have been wrapped by any higher
- level constructs like socket streams!
-
- :param obj: an object with a ``.fileno()`` method or an integer handle
- :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
- object to become readable
- :raises ~anyio.BusyResourceError: if another task is already waiting for the object
- to become readable
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().wait_readable(obj)
-
-
-def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]:
- """
- Wait until the given object can be written to.
-
- :param obj: an object with a ``.fileno()`` method or an integer handle
- :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
- object to become writable
- :raises ~anyio.BusyResourceError: if another task is already waiting for the object
- to become writable
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- .. seealso:: See the documentation of :func:`wait_readable` for the definition of
- ``obj`` and notes on backend compatibility.
-
- .. warning:: Don't use this on raw sockets that have been wrapped by any higher
- level constructs like socket streams!
-
- """
- return get_async_backend().wait_writable(obj)
-
-
-def notify_closing(obj: FileDescriptorLike) -> None:
- """
- Call this before closing a file descriptor (on Unix) or socket (on
- Windows). This will cause any `wait_readable` or `wait_writable`
- calls on the given object to immediately wake up and raise
- `~anyio.ClosedResourceError`.
-
- This doesn't actually close the object – you still have to do that
- yourself afterwards. Also, you want to be careful to make sure no
- new tasks start waiting on the object in between when you call this
- and when it's actually closed. So to close something properly, you
- usually want to do these steps in order:
-
- 1. Explicitly mark the object as closed, so that any new attempts
- to use it will abort before they start.
- 2. Call `notify_closing` to wake up any already-existing users.
- 3. Actually close the object.
-
- It's also possible to do them in a different order if that's more
- convenient, *but only if* you make sure not to have any checkpoints in
- between the steps. This way they all happen in a single atomic
- step, so other tasks won't be able to tell what order they happened
- in anyway.
-
- :param obj: an object with a ``.fileno()`` method or an integer handle
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- get_async_backend().notify_closing(obj)
-
-
-#
-# Private API
-#
-
-
-def convert_ipv6_sockaddr(
- sockaddr: tuple[str, int, int, int] | tuple[str, int],
-) -> tuple[str, int]:
- """
- Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format.
-
- If the scope ID is nonzero, it is added to the address, separated with ``%``.
- Otherwise the flow id and scope id are simply cut off from the tuple.
- Any other kinds of socket addresses are returned as-is.
-
- :param sockaddr: the result of :meth:`~socket.socket.getsockname`
- :return: the converted socket address
-
- """
- # This is more complicated than it should be because of MyPy
- if isinstance(sockaddr, tuple) and len(sockaddr) == 4:
- host, port, flowinfo, scope_id = sockaddr
- if scope_id:
- # PyPy (as of v7.3.11) leaves the interface name in the result, so
- # we discard it and only get the scope ID from the end
- # (https://foss.heptapod.net/pypy/pypy/-/issues/3938)
- host = host.split("%")[0]
-
- # Add scope_id to the address
- return f"{host}%{scope_id}", port
- else:
- return host, port
- else:
- return sockaddr
-
-
-async def setup_unix_local_socket(
- path: None | str | bytes | PathLike[Any],
- mode: int | None,
- socktype: int,
-) -> socket.socket:
- """
- Create a UNIX local socket object, deleting the socket at the given path if it
- exists.
-
- Not available on Windows.
-
- :param path: path of the socket
- :param mode: permissions to set on the socket
- :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM
-
- """
- path_str: str | None
- if path is not None:
- path_str = os.fsdecode(path)
-
- # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call
- if not path_str.startswith("\0"):
- # Copied from pathlib...
- try:
- stat_result = os.stat(path)
- except OSError as e:
- if e.errno not in (
- errno.ENOENT,
- errno.ENOTDIR,
- errno.EBADF,
- errno.ELOOP,
- ):
- raise
- else:
- if stat.S_ISSOCK(stat_result.st_mode):
- os.unlink(path)
- else:
- path_str = None
-
- raw_socket = socket.socket(socket.AF_UNIX, socktype)
- raw_socket.setblocking(False)
-
- if path_str is not None:
- try:
- await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True)
- if mode is not None:
- await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True)
- except BaseException:
- raw_socket.close()
- raise
-
- return raw_socket
-
-
-@dataclass
-class TCPConnectable(ByteStreamConnectable):
- """
- Connects to a TCP server at the given host and port.
-
- :param host: host name or IP address of the server
- :param port: TCP port number of the server
- """
-
- host: str | IPv4Address | IPv6Address
- port: int
-
- def __post_init__(self) -> None:
- if self.port < 1 or self.port > 65535:
- raise ValueError("TCP port number out of range")
-
- @override
- async def connect(self) -> SocketStream:
- try:
- return await connect_tcp(self.host, self.port)
- except OSError as exc:
- raise ConnectionFailed(
- f"error connecting to {self.host}:{self.port}: {exc}"
- ) from exc
-
-
-@dataclass
-class UNIXConnectable(ByteStreamConnectable):
- """
- Connects to a UNIX domain socket at the given path.
-
- :param path: the file system path of the socket
- """
-
- path: str | bytes | PathLike[str] | PathLike[bytes]
-
- @override
- async def connect(self) -> UNIXSocketStream:
- try:
- return await connect_unix(self.path)
- except OSError as exc:
- raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc
-
-
-def as_connectable(
- remote: ByteStreamConnectable
- | tuple[str | IPv4Address | IPv6Address, int]
- | str
- | bytes
- | PathLike[str],
- /,
- *,
- tls: bool = False,
- ssl_context: ssl.SSLContext | None = None,
- tls_hostname: str | None = None,
- tls_standard_compatible: bool = True,
-) -> ByteStreamConnectable:
- """
- Return a byte stream connectable from the given object.
-
- If a bytestream connectable is given, it is returned unchanged.
- If a tuple of (host, port) is given, a TCP connectable is returned.
- If a string or bytes path is given, a UNIX connectable is returned.
-
- If ``tls=True``, the connectable will be wrapped in a
- :class:`~.streams.tls.TLSConnectable`.
-
- :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket
- :param tls: if ``True``, wrap the plaintext connectable in a
- :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings)
- :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided,
- a secure default will be created)
- :param tls_hostname: if ``tls=True``, host name of the server to use for checking
- the server certificate (defaults to the host portion of the address for TCP
- connectables)
- :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream
- skip the closing handshake when closing the connection, so it won't raise an
- exception if the server does the same
-
- """
- connectable: TCPConnectable | UNIXConnectable | TLSConnectable
- if isinstance(remote, ByteStreamConnectable):
- return remote
- elif isinstance(remote, tuple) and len(remote) == 2:
- connectable = TCPConnectable(*remote)
- elif isinstance(remote, (str, bytes, PathLike)):
- connectable = UNIXConnectable(remote)
- else:
- raise TypeError(f"cannot convert {remote!r} to a connectable")
-
- if tls:
- if not tls_hostname and isinstance(connectable, TCPConnectable):
- tls_hostname = str(connectable.host)
-
- connectable = TLSConnectable(
- connectable,
- ssl_context=ssl_context,
- hostname=tls_hostname,
- standard_compatible=tls_standard_compatible,
- )
-
- return connectable
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_streams.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_streams.py
deleted file mode 100644
index 2b9c7df..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_streams.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from __future__ import annotations
-
-import math
-from typing import TypeVar
-from warnings import warn
-
-from ..streams.memory import (
- MemoryObjectReceiveStream,
- MemoryObjectSendStream,
- _MemoryObjectStreamState,
-)
-
-T_Item = TypeVar("T_Item")
-
-
-class create_memory_object_stream(
- tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]],
-):
- """
- Create a memory object stream.
-
- The stream's item type can be annotated like
- :func:`create_memory_object_stream[T_Item]`.
-
- :param max_buffer_size: number of items held in the buffer until ``send()`` starts
- blocking
- :param item_type: old way of marking the streams with the right generic type for
- static typing (does nothing on AnyIO 4)
-
- .. deprecated:: 4.0
- Use ``create_memory_object_stream[YourItemType](...)`` instead.
- :return: a tuple of (send stream, receive stream)
-
- """
-
- def __new__( # type: ignore[misc]
- cls, max_buffer_size: float = 0, item_type: object = None
- ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]:
- if max_buffer_size != math.inf and not isinstance(max_buffer_size, int):
- raise ValueError("max_buffer_size must be either an integer or math.inf")
- if max_buffer_size < 0:
- raise ValueError("max_buffer_size cannot be negative")
- if item_type is not None:
- warn(
- "The item_type argument has been deprecated in AnyIO 4.0. "
- "Use create_memory_object_stream[YourItemType](...) instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- state = _MemoryObjectStreamState[T_Item](max_buffer_size)
- return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state))
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py
deleted file mode 100644
index 36d9b30..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py
+++ /dev/null
@@ -1,202 +0,0 @@
-from __future__ import annotations
-
-import sys
-from collections.abc import AsyncIterable, Iterable, Mapping, Sequence
-from io import BytesIO
-from os import PathLike
-from subprocess import PIPE, CalledProcessError, CompletedProcess
-from typing import IO, Any, Union, cast
-
-from ..abc import Process
-from ._eventloop import get_async_backend
-from ._tasks import create_task_group
-
-if sys.version_info >= (3, 10):
- from typing import TypeAlias
-else:
- from typing_extensions import TypeAlias
-
-StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"]
-
-
-async def run_process(
- command: StrOrBytesPath | Sequence[StrOrBytesPath],
- *,
- input: bytes | None = None,
- stdin: int | IO[Any] | None = None,
- stdout: int | IO[Any] | None = PIPE,
- stderr: int | IO[Any] | None = PIPE,
- check: bool = True,
- cwd: StrOrBytesPath | None = None,
- env: Mapping[str, str] | None = None,
- startupinfo: Any = None,
- creationflags: int = 0,
- start_new_session: bool = False,
- pass_fds: Sequence[int] = (),
- user: str | int | None = None,
- group: str | int | None = None,
- extra_groups: Iterable[str | int] | None = None,
- umask: int = -1,
-) -> CompletedProcess[bytes]:
- """
- Run an external command in a subprocess and wait until it completes.
-
- .. seealso:: :func:`subprocess.run`
-
- :param command: either a string to pass to the shell, or an iterable of strings
- containing the executable name or path and its arguments
- :param input: bytes passed to the standard input of the subprocess
- :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
- a file-like object, or `None`; ``input`` overrides this
- :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
- a file-like object, or `None`
- :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
- :data:`subprocess.STDOUT`, a file-like object, or `None`
- :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the
- process terminates with a return code other than 0
- :param cwd: If not ``None``, change the working directory to this before running the
- command
- :param env: if not ``None``, this mapping replaces the inherited environment
- variables from the parent process
- :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
- to specify process startup parameters (Windows only)
- :param creationflags: flags that can be used to control the creation of the
- subprocess (see :class:`subprocess.Popen` for the specifics)
- :param start_new_session: if ``true`` the setsid() system call will be made in the
- child process prior to the execution of the subprocess. (POSIX only)
- :param pass_fds: sequence of file descriptors to keep open between the parent and
- child processes. (POSIX only)
- :param user: effective user to run the process as (Python >= 3.9, POSIX only)
- :param group: effective group to run the process as (Python >= 3.9, POSIX only)
- :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9,
- POSIX only)
- :param umask: if not negative, this umask is applied in the child process before
- running the given command (Python >= 3.9, POSIX only)
- :return: an object representing the completed process
- :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process
- exits with a nonzero return code
-
- """
-
- async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None:
- buffer = BytesIO()
- async for chunk in stream:
- buffer.write(chunk)
-
- stream_contents[index] = buffer.getvalue()
-
- if stdin is not None and input is not None:
- raise ValueError("only one of stdin and input is allowed")
-
- async with await open_process(
- command,
- stdin=PIPE if input else stdin,
- stdout=stdout,
- stderr=stderr,
- cwd=cwd,
- env=env,
- startupinfo=startupinfo,
- creationflags=creationflags,
- start_new_session=start_new_session,
- pass_fds=pass_fds,
- user=user,
- group=group,
- extra_groups=extra_groups,
- umask=umask,
- ) as process:
- stream_contents: list[bytes | None] = [None, None]
- async with create_task_group() as tg:
- if process.stdout:
- tg.start_soon(drain_stream, process.stdout, 0)
-
- if process.stderr:
- tg.start_soon(drain_stream, process.stderr, 1)
-
- if process.stdin and input:
- await process.stdin.send(input)
- await process.stdin.aclose()
-
- await process.wait()
-
- output, errors = stream_contents
- if check and process.returncode != 0:
- raise CalledProcessError(cast(int, process.returncode), command, output, errors)
-
- return CompletedProcess(command, cast(int, process.returncode), output, errors)
-
-
-async def open_process(
- command: StrOrBytesPath | Sequence[StrOrBytesPath],
- *,
- stdin: int | IO[Any] | None = PIPE,
- stdout: int | IO[Any] | None = PIPE,
- stderr: int | IO[Any] | None = PIPE,
- cwd: StrOrBytesPath | None = None,
- env: Mapping[str, str] | None = None,
- startupinfo: Any = None,
- creationflags: int = 0,
- start_new_session: bool = False,
- pass_fds: Sequence[int] = (),
- user: str | int | None = None,
- group: str | int | None = None,
- extra_groups: Iterable[str | int] | None = None,
- umask: int = -1,
-) -> Process:
- """
- Start an external command in a subprocess.
-
- .. seealso:: :class:`subprocess.Popen`
-
- :param command: either a string to pass to the shell, or an iterable of strings
- containing the executable name or path and its arguments
- :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a
- file-like object, or ``None``
- :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
- a file-like object, or ``None``
- :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
- :data:`subprocess.STDOUT`, a file-like object, or ``None``
- :param cwd: If not ``None``, the working directory is changed before executing
- :param env: If env is not ``None``, it must be a mapping that defines the
- environment variables for the new process
- :param creationflags: flags that can be used to control the creation of the
- subprocess (see :class:`subprocess.Popen` for the specifics)
- :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
- to specify process startup parameters (Windows only)
- :param start_new_session: if ``true`` the setsid() system call will be made in the
- child process prior to the execution of the subprocess. (POSIX only)
- :param pass_fds: sequence of file descriptors to keep open between the parent and
- child processes. (POSIX only)
- :param user: effective user to run the process as (POSIX only)
- :param group: effective group to run the process as (POSIX only)
- :param extra_groups: supplementary groups to set in the subprocess (POSIX only)
- :param umask: if not negative, this umask is applied in the child process before
- running the given command (POSIX only)
- :return: an asynchronous process object
-
- """
- kwargs: dict[str, Any] = {}
- if user is not None:
- kwargs["user"] = user
-
- if group is not None:
- kwargs["group"] = group
-
- if extra_groups is not None:
- kwargs["extra_groups"] = group
-
- if umask >= 0:
- kwargs["umask"] = umask
-
- return await get_async_backend().open_process(
- command,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- cwd=cwd,
- env=env,
- startupinfo=startupinfo,
- creationflags=creationflags,
- start_new_session=start_new_session,
- pass_fds=pass_fds,
- **kwargs,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py
deleted file mode 100644
index c0ef27a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py
+++ /dev/null
@@ -1,753 +0,0 @@
-from __future__ import annotations
-
-import math
-from collections import deque
-from collections.abc import Callable
-from dataclasses import dataclass
-from types import TracebackType
-from typing import TypeVar
-
-from ..lowlevel import checkpoint_if_cancelled
-from ._eventloop import get_async_backend
-from ._exceptions import BusyResourceError, NoEventLoopError
-from ._tasks import CancelScope
-from ._testing import TaskInfo, get_current_task
-
-T = TypeVar("T")
-
-
-@dataclass(frozen=True)
-class EventStatistics:
- """
- :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait`
- """
-
- tasks_waiting: int
-
-
-@dataclass(frozen=True)
-class CapacityLimiterStatistics:
- """
- :ivar int borrowed_tokens: number of tokens currently borrowed by tasks
- :ivar float total_tokens: total number of available tokens
- :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from
- this limiter
- :ivar int tasks_waiting: number of tasks waiting on
- :meth:`~.CapacityLimiter.acquire` or
- :meth:`~.CapacityLimiter.acquire_on_behalf_of`
- """
-
- borrowed_tokens: int
- total_tokens: float
- borrowers: tuple[object, ...]
- tasks_waiting: int
-
-
-@dataclass(frozen=True)
-class LockStatistics:
- """
- :ivar bool locked: flag indicating if this lock is locked or not
- :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the
- lock is not held by any task)
- :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire`
- """
-
- locked: bool
- owner: TaskInfo | None
- tasks_waiting: int
-
-
-@dataclass(frozen=True)
-class ConditionStatistics:
- """
- :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait`
- :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying
- :class:`~.Lock`
- """
-
- tasks_waiting: int
- lock_statistics: LockStatistics
-
-
-@dataclass(frozen=True)
-class SemaphoreStatistics:
- """
- :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire`
-
- """
-
- tasks_waiting: int
-
-
-class Event:
- def __new__(cls) -> Event:
- try:
- return get_async_backend().create_event()
- except NoEventLoopError:
- return EventAdapter()
-
- def set(self) -> None:
- """Set the flag, notifying all listeners."""
- raise NotImplementedError
-
- def is_set(self) -> bool:
- """Return ``True`` if the flag is set, ``False`` if not."""
- raise NotImplementedError
-
- async def wait(self) -> None:
- """
- Wait until the flag has been set.
-
- If the flag has already been set when this method is called, it returns
- immediately.
-
- """
- raise NotImplementedError
-
- def statistics(self) -> EventStatistics:
- """Return statistics about the current state of this event."""
- raise NotImplementedError
-
-
-class EventAdapter(Event):
- _internal_event: Event | None = None
- _is_set: bool = False
-
- def __new__(cls) -> EventAdapter:
- return object.__new__(cls)
-
- @property
- def _event(self) -> Event:
- if self._internal_event is None:
- self._internal_event = get_async_backend().create_event()
- if self._is_set:
- self._internal_event.set()
-
- return self._internal_event
-
- def set(self) -> None:
- if self._internal_event is None:
- self._is_set = True
- else:
- self._event.set()
-
- def is_set(self) -> bool:
- if self._internal_event is None:
- return self._is_set
-
- return self._internal_event.is_set()
-
- async def wait(self) -> None:
- await self._event.wait()
-
- def statistics(self) -> EventStatistics:
- if self._internal_event is None:
- return EventStatistics(tasks_waiting=0)
-
- return self._internal_event.statistics()
-
-
-class Lock:
- def __new__(cls, *, fast_acquire: bool = False) -> Lock:
- try:
- return get_async_backend().create_lock(fast_acquire=fast_acquire)
- except NoEventLoopError:
- return LockAdapter(fast_acquire=fast_acquire)
-
- async def __aenter__(self) -> None:
- await self.acquire()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.release()
-
- async def acquire(self) -> None:
- """Acquire the lock."""
- raise NotImplementedError
-
- def acquire_nowait(self) -> None:
- """
- Acquire the lock, without blocking.
-
- :raises ~anyio.WouldBlock: if the operation would block
-
- """
- raise NotImplementedError
-
- def release(self) -> None:
- """Release the lock."""
- raise NotImplementedError
-
- def locked(self) -> bool:
- """Return True if the lock is currently held."""
- raise NotImplementedError
-
- def statistics(self) -> LockStatistics:
- """
- Return statistics about the current state of this lock.
-
- .. versionadded:: 3.0
- """
- raise NotImplementedError
-
-
-class LockAdapter(Lock):
- _internal_lock: Lock | None = None
-
- def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter:
- return object.__new__(cls)
-
- def __init__(self, *, fast_acquire: bool = False):
- self._fast_acquire = fast_acquire
-
- @property
- def _lock(self) -> Lock:
- if self._internal_lock is None:
- self._internal_lock = get_async_backend().create_lock(
- fast_acquire=self._fast_acquire
- )
-
- return self._internal_lock
-
- async def __aenter__(self) -> None:
- await self._lock.acquire()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- if self._internal_lock is not None:
- self._internal_lock.release()
-
- async def acquire(self) -> None:
- """Acquire the lock."""
- await self._lock.acquire()
-
- def acquire_nowait(self) -> None:
- """
- Acquire the lock, without blocking.
-
- :raises ~anyio.WouldBlock: if the operation would block
-
- """
- self._lock.acquire_nowait()
-
- def release(self) -> None:
- """Release the lock."""
- self._lock.release()
-
- def locked(self) -> bool:
- """Return True if the lock is currently held."""
- return self._lock.locked()
-
- def statistics(self) -> LockStatistics:
- """
- Return statistics about the current state of this lock.
-
- .. versionadded:: 3.0
-
- """
- if self._internal_lock is None:
- return LockStatistics(False, None, 0)
-
- return self._internal_lock.statistics()
-
-
-class Condition:
- _owner_task: TaskInfo | None = None
-
- def __init__(self, lock: Lock | None = None):
- self._lock = lock or Lock()
- self._waiters: deque[Event] = deque()
-
- async def __aenter__(self) -> None:
- await self.acquire()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.release()
-
- def _check_acquired(self) -> None:
- if self._owner_task != get_current_task():
- raise RuntimeError("The current task is not holding the underlying lock")
-
- async def acquire(self) -> None:
- """Acquire the underlying lock."""
- await self._lock.acquire()
- self._owner_task = get_current_task()
-
- def acquire_nowait(self) -> None:
- """
- Acquire the underlying lock, without blocking.
-
- :raises ~anyio.WouldBlock: if the operation would block
-
- """
- self._lock.acquire_nowait()
- self._owner_task = get_current_task()
-
- def release(self) -> None:
- """Release the underlying lock."""
- self._lock.release()
-
- def locked(self) -> bool:
- """Return True if the lock is set."""
- return self._lock.locked()
-
- def notify(self, n: int = 1) -> None:
- """Notify exactly n listeners."""
- self._check_acquired()
- for _ in range(n):
- try:
- event = self._waiters.popleft()
- except IndexError:
- break
-
- event.set()
-
- def notify_all(self) -> None:
- """Notify all the listeners."""
- self._check_acquired()
- for event in self._waiters:
- event.set()
-
- self._waiters.clear()
-
- async def wait(self) -> None:
- """Wait for a notification."""
- await checkpoint_if_cancelled()
- self._check_acquired()
- event = Event()
- self._waiters.append(event)
- self.release()
- try:
- await event.wait()
- except BaseException:
- if not event.is_set():
- self._waiters.remove(event)
-
- raise
- finally:
- with CancelScope(shield=True):
- await self.acquire()
-
- async def wait_for(self, predicate: Callable[[], T]) -> T:
- """
- Wait until a predicate becomes true.
-
- :param predicate: a callable that returns a truthy value when the condition is
- met
- :return: the result of the predicate
-
- .. versionadded:: 4.11.0
-
- """
- while not (result := predicate()):
- await self.wait()
-
- return result
-
- def statistics(self) -> ConditionStatistics:
- """
- Return statistics about the current state of this condition.
-
- .. versionadded:: 3.0
- """
- return ConditionStatistics(len(self._waiters), self._lock.statistics())
-
-
-class Semaphore:
- def __new__(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> Semaphore:
- try:
- return get_async_backend().create_semaphore(
- initial_value, max_value=max_value, fast_acquire=fast_acquire
- )
- except NoEventLoopError:
- return SemaphoreAdapter(initial_value, max_value=max_value)
-
- def __init__(
- self,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ):
- if not isinstance(initial_value, int):
- raise TypeError("initial_value must be an integer")
- if initial_value < 0:
- raise ValueError("initial_value must be >= 0")
- if max_value is not None:
- if not isinstance(max_value, int):
- raise TypeError("max_value must be an integer or None")
- if max_value < initial_value:
- raise ValueError(
- "max_value must be equal to or higher than initial_value"
- )
-
- self._fast_acquire = fast_acquire
-
- async def __aenter__(self) -> Semaphore:
- await self.acquire()
- return self
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.release()
-
- async def acquire(self) -> None:
- """Decrement the semaphore value, blocking if necessary."""
- raise NotImplementedError
-
- def acquire_nowait(self) -> None:
- """
- Acquire the underlying lock, without blocking.
-
- :raises ~anyio.WouldBlock: if the operation would block
-
- """
- raise NotImplementedError
-
- def release(self) -> None:
- """Increment the semaphore value."""
- raise NotImplementedError
-
- @property
- def value(self) -> int:
- """The current value of the semaphore."""
- raise NotImplementedError
-
- @property
- def max_value(self) -> int | None:
- """The maximum value of the semaphore."""
- raise NotImplementedError
-
- def statistics(self) -> SemaphoreStatistics:
- """
- Return statistics about the current state of this semaphore.
-
- .. versionadded:: 3.0
- """
- raise NotImplementedError
-
-
-class SemaphoreAdapter(Semaphore):
- _internal_semaphore: Semaphore | None = None
-
- def __new__(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> SemaphoreAdapter:
- return object.__new__(cls)
-
- def __init__(
- self,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> None:
- super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
- self._initial_value = initial_value
- self._max_value = max_value
-
- @property
- def _semaphore(self) -> Semaphore:
- if self._internal_semaphore is None:
- self._internal_semaphore = get_async_backend().create_semaphore(
- self._initial_value, max_value=self._max_value
- )
-
- return self._internal_semaphore
-
- async def acquire(self) -> None:
- await self._semaphore.acquire()
-
- def acquire_nowait(self) -> None:
- self._semaphore.acquire_nowait()
-
- def release(self) -> None:
- self._semaphore.release()
-
- @property
- def value(self) -> int:
- if self._internal_semaphore is None:
- return self._initial_value
-
- return self._semaphore.value
-
- @property
- def max_value(self) -> int | None:
- return self._max_value
-
- def statistics(self) -> SemaphoreStatistics:
- if self._internal_semaphore is None:
- return SemaphoreStatistics(tasks_waiting=0)
-
- return self._semaphore.statistics()
-
-
-class CapacityLimiter:
- def __new__(cls, total_tokens: float) -> CapacityLimiter:
- try:
- return get_async_backend().create_capacity_limiter(total_tokens)
- except NoEventLoopError:
- return CapacityLimiterAdapter(total_tokens)
-
- async def __aenter__(self) -> None:
- raise NotImplementedError
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- raise NotImplementedError
-
- @property
- def total_tokens(self) -> float:
- """
- The total number of tokens available for borrowing.
-
- This is a read-write property. If the total number of tokens is increased, the
- proportionate number of tasks waiting on this limiter will be granted their
- tokens.
-
- .. versionchanged:: 3.0
- The property is now writable.
- .. versionchanged:: 4.12
- The value can now be set to 0.
-
- """
- raise NotImplementedError
-
- @total_tokens.setter
- def total_tokens(self, value: float) -> None:
- raise NotImplementedError
-
- @property
- def borrowed_tokens(self) -> int:
- """The number of tokens that have currently been borrowed."""
- raise NotImplementedError
-
- @property
- def available_tokens(self) -> float:
- """The number of tokens currently available to be borrowed"""
- raise NotImplementedError
-
- def acquire_nowait(self) -> None:
- """
- Acquire a token for the current task without waiting for one to become
- available.
-
- :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
-
- """
- raise NotImplementedError
-
- def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
- """
- Acquire a token without waiting for one to become available.
-
- :param borrower: the entity borrowing a token
- :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
-
- """
- raise NotImplementedError
-
- async def acquire(self) -> None:
- """
- Acquire a token for the current task, waiting if necessary for one to become
- available.
-
- """
- raise NotImplementedError
-
- async def acquire_on_behalf_of(self, borrower: object) -> None:
- """
- Acquire a token, waiting if necessary for one to become available.
-
- :param borrower: the entity borrowing a token
-
- """
- raise NotImplementedError
-
- def release(self) -> None:
- """
- Release the token held by the current task.
-
- :raises RuntimeError: if the current task has not borrowed a token from this
- limiter.
-
- """
- raise NotImplementedError
-
- def release_on_behalf_of(self, borrower: object) -> None:
- """
- Release the token held by the given borrower.
-
- :raises RuntimeError: if the borrower has not borrowed a token from this
- limiter.
-
- """
- raise NotImplementedError
-
- def statistics(self) -> CapacityLimiterStatistics:
- """
- Return statistics about the current state of this limiter.
-
- .. versionadded:: 3.0
-
- """
- raise NotImplementedError
-
-
-class CapacityLimiterAdapter(CapacityLimiter):
- _internal_limiter: CapacityLimiter | None = None
-
- def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter:
- return object.__new__(cls)
-
- def __init__(self, total_tokens: float) -> None:
- self.total_tokens = total_tokens
-
- @property
- def _limiter(self) -> CapacityLimiter:
- if self._internal_limiter is None:
- self._internal_limiter = get_async_backend().create_capacity_limiter(
- self._total_tokens
- )
-
- return self._internal_limiter
-
- async def __aenter__(self) -> None:
- await self._limiter.__aenter__()
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- return await self._limiter.__aexit__(exc_type, exc_val, exc_tb)
-
- @property
- def total_tokens(self) -> float:
- if self._internal_limiter is None:
- return self._total_tokens
-
- return self._internal_limiter.total_tokens
-
- @total_tokens.setter
- def total_tokens(self, value: float) -> None:
- if not isinstance(value, int) and value is not math.inf:
- raise TypeError("total_tokens must be an int or math.inf")
- elif value < 1:
- raise ValueError("total_tokens must be >= 1")
-
- if self._internal_limiter is None:
- self._total_tokens = value
- return
-
- self._limiter.total_tokens = value
-
- @property
- def borrowed_tokens(self) -> int:
- if self._internal_limiter is None:
- return 0
-
- return self._internal_limiter.borrowed_tokens
-
- @property
- def available_tokens(self) -> float:
- if self._internal_limiter is None:
- return self._total_tokens
-
- return self._internal_limiter.available_tokens
-
- def acquire_nowait(self) -> None:
- self._limiter.acquire_nowait()
-
- def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
- self._limiter.acquire_on_behalf_of_nowait(borrower)
-
- async def acquire(self) -> None:
- await self._limiter.acquire()
-
- async def acquire_on_behalf_of(self, borrower: object) -> None:
- await self._limiter.acquire_on_behalf_of(borrower)
-
- def release(self) -> None:
- self._limiter.release()
-
- def release_on_behalf_of(self, borrower: object) -> None:
- self._limiter.release_on_behalf_of(borrower)
-
- def statistics(self) -> CapacityLimiterStatistics:
- if self._internal_limiter is None:
- return CapacityLimiterStatistics(
- borrowed_tokens=0,
- total_tokens=self.total_tokens,
- borrowers=(),
- tasks_waiting=0,
- )
-
- return self._internal_limiter.statistics()
-
-
-class ResourceGuard:
- """
- A context manager for ensuring that a resource is only used by a single task at a
- time.
-
- Entering this context manager while the previous has not exited it yet will trigger
- :exc:`BusyResourceError`.
-
- :param action: the action to guard against (visible in the :exc:`BusyResourceError`
- when triggered, e.g. "Another task is already {action} this resource")
-
- .. versionadded:: 4.1
- """
-
- __slots__ = "action", "_guarded"
-
- def __init__(self, action: str = "using"):
- self.action: str = action
- self._guarded = False
-
- def __enter__(self) -> None:
- if self._guarded:
- raise BusyResourceError(self.action)
-
- self._guarded = True
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self._guarded = False
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tasks.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tasks.py
deleted file mode 100644
index 0688bfe..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tasks.py
+++ /dev/null
@@ -1,173 +0,0 @@
-from __future__ import annotations
-
-import math
-from collections.abc import Generator
-from contextlib import contextmanager
-from types import TracebackType
-
-from ..abc._tasks import TaskGroup, TaskStatus
-from ._eventloop import get_async_backend
-
-
-class _IgnoredTaskStatus(TaskStatus[object]):
- def started(self, value: object = None) -> None:
- pass
-
-
-TASK_STATUS_IGNORED = _IgnoredTaskStatus()
-
-
-class CancelScope:
- """
- Wraps a unit of work that can be made separately cancellable.
-
- :param deadline: The time (clock value) when this scope is cancelled automatically
- :param shield: ``True`` to shield the cancel scope from external cancellation
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
- """
-
- def __new__(
- cls, *, deadline: float = math.inf, shield: bool = False
- ) -> CancelScope:
- return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline)
-
- def cancel(self, reason: str | None = None) -> None:
- """
- Cancel this scope immediately.
-
- :param reason: a message describing the reason for the cancellation
-
- """
- raise NotImplementedError
-
- @property
- def deadline(self) -> float:
- """
- The time (clock value) when this scope is cancelled automatically.
-
- Will be ``float('inf')`` if no timeout has been set.
-
- """
- raise NotImplementedError
-
- @deadline.setter
- def deadline(self, value: float) -> None:
- raise NotImplementedError
-
- @property
- def cancel_called(self) -> bool:
- """``True`` if :meth:`cancel` has been called."""
- raise NotImplementedError
-
- @property
- def cancelled_caught(self) -> bool:
- """
- ``True`` if this scope suppressed a cancellation exception it itself raised.
-
- This is typically used to check if any work was interrupted, or to see if the
- scope was cancelled due to its deadline being reached. The value will, however,
- only be ``True`` if the cancellation was triggered by the scope itself (and not
- an outer scope).
-
- """
- raise NotImplementedError
-
- @property
- def shield(self) -> bool:
- """
- ``True`` if this scope is shielded from external cancellation.
-
- While a scope is shielded, it will not receive cancellations from outside.
-
- """
- raise NotImplementedError
-
- @shield.setter
- def shield(self, value: bool) -> None:
- raise NotImplementedError
-
- def __enter__(self) -> CancelScope:
- raise NotImplementedError
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- raise NotImplementedError
-
-
-@contextmanager
-def fail_after(
- delay: float | None, shield: bool = False
-) -> Generator[CancelScope, None, None]:
- """
- Create a context manager which raises a :class:`TimeoutError` if does not finish in
- time.
-
- :param delay: maximum allowed time (in seconds) before raising the exception, or
- ``None`` to disable the timeout
- :param shield: ``True`` to shield the cancel scope from external cancellation
- :return: a context manager that yields a cancel scope
- :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\]
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- current_time = get_async_backend().current_time
- deadline = (current_time() + delay) if delay is not None else math.inf
- with get_async_backend().create_cancel_scope(
- deadline=deadline, shield=shield
- ) as cancel_scope:
- yield cancel_scope
-
- if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline:
- raise TimeoutError
-
-
-def move_on_after(delay: float | None, shield: bool = False) -> CancelScope:
- """
- Create a cancel scope with a deadline that expires after the given delay.
-
- :param delay: maximum allowed time (in seconds) before exiting the context block, or
- ``None`` to disable the timeout
- :param shield: ``True`` to shield the cancel scope from external cancellation
- :return: a cancel scope
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- deadline = (
- (get_async_backend().current_time() + delay) if delay is not None else math.inf
- )
- return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield)
-
-
-def current_effective_deadline() -> float:
- """
- Return the nearest deadline among all the cancel scopes effective for the current
- task.
-
- :return: a clock value from the event loop's internal clock (or ``float('inf')`` if
- there is no deadline in effect, or ``float('-inf')`` if the current scope has
- been cancelled)
- :rtype: float
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().current_effective_deadline()
-
-
-def create_task_group() -> TaskGroup:
- """
- Create a task group.
-
- :return: a task group
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().create_task_group()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py
deleted file mode 100644
index fbb6b14..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py
+++ /dev/null
@@ -1,616 +0,0 @@
-from __future__ import annotations
-
-import os
-import sys
-import tempfile
-from collections.abc import Iterable
-from io import BytesIO, TextIOWrapper
-from types import TracebackType
-from typing import (
- TYPE_CHECKING,
- Any,
- AnyStr,
- Generic,
- overload,
-)
-
-from .. import to_thread
-from .._core._fileio import AsyncFile
-from ..lowlevel import checkpoint_if_cancelled
-
-if TYPE_CHECKING:
- from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
-
-
-class TemporaryFile(Generic[AnyStr]):
- """
- An asynchronous temporary file that is automatically created and cleaned up.
-
- This class provides an asynchronous context manager interface to a temporary file.
- The file is created using Python's standard `tempfile.TemporaryFile` function in a
- background thread, and is wrapped as an asynchronous file using `AsyncFile`.
-
- :param mode: The mode in which the file is opened. Defaults to "w+b".
- :param buffering: The buffering policy (-1 means the default buffering).
- :param encoding: The encoding used to decode or encode the file. Only applicable in
- text mode.
- :param newline: Controls how universal newlines mode works (only applicable in text
- mode).
- :param suffix: The suffix for the temporary file name.
- :param prefix: The prefix for the temporary file name.
- :param dir: The directory in which the temporary file is created.
- :param errors: The error handling scheme used for encoding/decoding errors.
- """
-
- _async_file: AsyncFile[AnyStr]
-
- @overload
- def __init__(
- self: TemporaryFile[bytes],
- mode: OpenBinaryMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- *,
- errors: str | None = ...,
- ): ...
- @overload
- def __init__(
- self: TemporaryFile[str],
- mode: OpenTextMode,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- *,
- errors: str | None = ...,
- ): ...
-
- def __init__(
- self,
- mode: OpenTextMode | OpenBinaryMode = "w+b",
- buffering: int = -1,
- encoding: str | None = None,
- newline: str | None = None,
- suffix: str | None = None,
- prefix: str | None = None,
- dir: str | None = None,
- *,
- errors: str | None = None,
- ) -> None:
- self.mode = mode
- self.buffering = buffering
- self.encoding = encoding
- self.newline = newline
- self.suffix: str | None = suffix
- self.prefix: str | None = prefix
- self.dir: str | None = dir
- self.errors = errors
-
- async def __aenter__(self) -> AsyncFile[AnyStr]:
- fp = await to_thread.run_sync(
- lambda: tempfile.TemporaryFile(
- self.mode,
- self.buffering,
- self.encoding,
- self.newline,
- self.suffix,
- self.prefix,
- self.dir,
- errors=self.errors,
- )
- )
- self._async_file = AsyncFile(fp)
- return self._async_file
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- traceback: TracebackType | None,
- ) -> None:
- await self._async_file.aclose()
-
-
-class NamedTemporaryFile(Generic[AnyStr]):
- """
- An asynchronous named temporary file that is automatically created and cleaned up.
-
- This class provides an asynchronous context manager for a temporary file with a
- visible name in the file system. It uses Python's standard
- :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with
- :class:`AsyncFile` for asynchronous operations.
-
- :param mode: The mode in which the file is opened. Defaults to "w+b".
- :param buffering: The buffering policy (-1 means the default buffering).
- :param encoding: The encoding used to decode or encode the file. Only applicable in
- text mode.
- :param newline: Controls how universal newlines mode works (only applicable in text
- mode).
- :param suffix: The suffix for the temporary file name.
- :param prefix: The prefix for the temporary file name.
- :param dir: The directory in which the temporary file is created.
- :param delete: Whether to delete the file when it is closed.
- :param errors: The error handling scheme used for encoding/decoding errors.
- :param delete_on_close: (Python 3.12+) Whether to delete the file on close.
- """
-
- _async_file: AsyncFile[AnyStr]
-
- @overload
- def __init__(
- self: NamedTemporaryFile[bytes],
- mode: OpenBinaryMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- delete: bool = ...,
- *,
- errors: str | None = ...,
- delete_on_close: bool = ...,
- ): ...
- @overload
- def __init__(
- self: NamedTemporaryFile[str],
- mode: OpenTextMode,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- delete: bool = ...,
- *,
- errors: str | None = ...,
- delete_on_close: bool = ...,
- ): ...
-
- def __init__(
- self,
- mode: OpenBinaryMode | OpenTextMode = "w+b",
- buffering: int = -1,
- encoding: str | None = None,
- newline: str | None = None,
- suffix: str | None = None,
- prefix: str | None = None,
- dir: str | None = None,
- delete: bool = True,
- *,
- errors: str | None = None,
- delete_on_close: bool = True,
- ) -> None:
- self._params: dict[str, Any] = {
- "mode": mode,
- "buffering": buffering,
- "encoding": encoding,
- "newline": newline,
- "suffix": suffix,
- "prefix": prefix,
- "dir": dir,
- "delete": delete,
- "errors": errors,
- }
- if sys.version_info >= (3, 12):
- self._params["delete_on_close"] = delete_on_close
-
- async def __aenter__(self) -> AsyncFile[AnyStr]:
- fp = await to_thread.run_sync(
- lambda: tempfile.NamedTemporaryFile(**self._params)
- )
- self._async_file = AsyncFile(fp)
- return self._async_file
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- traceback: TracebackType | None,
- ) -> None:
- await self._async_file.aclose()
-
-
-class SpooledTemporaryFile(AsyncFile[AnyStr]):
- """
- An asynchronous spooled temporary file that starts in memory and is spooled to disk.
-
- This class provides an asynchronous interface to a spooled temporary file, much like
- Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous
- write operations and provides a method to force a rollover to disk.
-
- :param max_size: Maximum size in bytes before the file is rolled over to disk.
- :param mode: The mode in which the file is opened. Defaults to "w+b".
- :param buffering: The buffering policy (-1 means the default buffering).
- :param encoding: The encoding used to decode or encode the file (text mode only).
- :param newline: Controls how universal newlines mode works (text mode only).
- :param suffix: The suffix for the temporary file name.
- :param prefix: The prefix for the temporary file name.
- :param dir: The directory in which the temporary file is created.
- :param errors: The error handling scheme used for encoding/decoding errors.
- """
-
- _rolled: bool = False
-
- @overload
- def __init__(
- self: SpooledTemporaryFile[bytes],
- max_size: int = ...,
- mode: OpenBinaryMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- *,
- errors: str | None = ...,
- ): ...
- @overload
- def __init__(
- self: SpooledTemporaryFile[str],
- max_size: int = ...,
- mode: OpenTextMode = ...,
- buffering: int = ...,
- encoding: str | None = ...,
- newline: str | None = ...,
- suffix: str | None = ...,
- prefix: str | None = ...,
- dir: str | None = ...,
- *,
- errors: str | None = ...,
- ): ...
-
- def __init__(
- self,
- max_size: int = 0,
- mode: OpenBinaryMode | OpenTextMode = "w+b",
- buffering: int = -1,
- encoding: str | None = None,
- newline: str | None = None,
- suffix: str | None = None,
- prefix: str | None = None,
- dir: str | None = None,
- *,
- errors: str | None = None,
- ) -> None:
- self._tempfile_params: dict[str, Any] = {
- "mode": mode,
- "buffering": buffering,
- "encoding": encoding,
- "newline": newline,
- "suffix": suffix,
- "prefix": prefix,
- "dir": dir,
- "errors": errors,
- }
- self._max_size = max_size
- if "b" in mode:
- super().__init__(BytesIO()) # type: ignore[arg-type]
- else:
- super().__init__(
- TextIOWrapper( # type: ignore[arg-type]
- BytesIO(),
- encoding=encoding,
- errors=errors,
- newline=newline,
- write_through=True,
- )
- )
-
- async def aclose(self) -> None:
- if not self._rolled:
- self._fp.close()
- return
-
- await super().aclose()
-
- async def _check(self) -> None:
- if self._rolled or self._fp.tell() <= self._max_size:
- return
-
- await self.rollover()
-
- async def rollover(self) -> None:
- if self._rolled:
- return
-
- self._rolled = True
- buffer = self._fp
- buffer.seek(0)
- self._fp = await to_thread.run_sync(
- lambda: tempfile.TemporaryFile(**self._tempfile_params)
- )
- await self.write(buffer.read())
- buffer.close()
-
- @property
- def closed(self) -> bool:
- return self._fp.closed
-
- async def read(self, size: int = -1) -> AnyStr:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.read(size)
-
- return await super().read(size) # type: ignore[return-value]
-
- async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.read1(size)
-
- return await super().read1(size)
-
- async def readline(self) -> AnyStr:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.readline()
-
- return await super().readline() # type: ignore[return-value]
-
- async def readlines(self) -> list[AnyStr]:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.readlines()
-
- return await super().readlines() # type: ignore[return-value]
-
- async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int:
- if not self._rolled:
- await checkpoint_if_cancelled()
- self._fp.readinto(b)
-
- return await super().readinto(b)
-
- async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int:
- if not self._rolled:
- await checkpoint_if_cancelled()
- self._fp.readinto(b)
-
- return await super().readinto1(b)
-
- async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.seek(offset, whence)
-
- return await super().seek(offset, whence)
-
- async def tell(self) -> int:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.tell()
-
- return await super().tell()
-
- async def truncate(self, size: int | None = None) -> int:
- if not self._rolled:
- await checkpoint_if_cancelled()
- return self._fp.truncate(size)
-
- return await super().truncate(size)
-
- @overload
- async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ...
- @overload
- async def write(self: SpooledTemporaryFile[str], b: str) -> int: ...
-
- async def write(self, b: ReadableBuffer | str) -> int:
- """
- Asynchronously write data to the spooled temporary file.
-
- If the file has not yet been rolled over, the data is written synchronously,
- and a rollover is triggered if the size exceeds the maximum size.
-
- :param s: The data to write.
- :return: The number of bytes written.
- :raises RuntimeError: If the underlying file is not initialized.
-
- """
- if not self._rolled:
- await checkpoint_if_cancelled()
- result = self._fp.write(b)
- await self._check()
- return result
-
- return await super().write(b) # type: ignore[misc]
-
- @overload
- async def writelines(
- self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer]
- ) -> None: ...
- @overload
- async def writelines(
- self: SpooledTemporaryFile[str], lines: Iterable[str]
- ) -> None: ...
-
- async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None:
- """
- Asynchronously write a list of lines to the spooled temporary file.
-
- If the file has not yet been rolled over, the lines are written synchronously,
- and a rollover is triggered if the size exceeds the maximum size.
-
- :param lines: An iterable of lines to write.
- :raises RuntimeError: If the underlying file is not initialized.
-
- """
- if not self._rolled:
- await checkpoint_if_cancelled()
- result = self._fp.writelines(lines)
- await self._check()
- return result
-
- return await super().writelines(lines) # type: ignore[misc]
-
-
-class TemporaryDirectory(Generic[AnyStr]):
- """
- An asynchronous temporary directory that is created and cleaned up automatically.
-
- This class provides an asynchronous context manager for creating a temporary
- directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to
- perform directory creation and cleanup operations in a background thread.
-
- :param suffix: Suffix to be added to the temporary directory name.
- :param prefix: Prefix to be added to the temporary directory name.
- :param dir: The parent directory where the temporary directory is created.
- :param ignore_cleanup_errors: Whether to ignore errors during cleanup
- (Python 3.10+).
- :param delete: Whether to delete the directory upon closing (Python 3.12+).
- """
-
- def __init__(
- self,
- suffix: AnyStr | None = None,
- prefix: AnyStr | None = None,
- dir: AnyStr | None = None,
- *,
- ignore_cleanup_errors: bool = False,
- delete: bool = True,
- ) -> None:
- self.suffix: AnyStr | None = suffix
- self.prefix: AnyStr | None = prefix
- self.dir: AnyStr | None = dir
- self.ignore_cleanup_errors = ignore_cleanup_errors
- self.delete = delete
-
- self._tempdir: tempfile.TemporaryDirectory | None = None
-
- async def __aenter__(self) -> str:
- params: dict[str, Any] = {
- "suffix": self.suffix,
- "prefix": self.prefix,
- "dir": self.dir,
- }
- if sys.version_info >= (3, 10):
- params["ignore_cleanup_errors"] = self.ignore_cleanup_errors
-
- if sys.version_info >= (3, 12):
- params["delete"] = self.delete
-
- self._tempdir = await to_thread.run_sync(
- lambda: tempfile.TemporaryDirectory(**params)
- )
- return await to_thread.run_sync(self._tempdir.__enter__)
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- traceback: TracebackType | None,
- ) -> None:
- if self._tempdir is not None:
- await to_thread.run_sync(
- self._tempdir.__exit__, exc_type, exc_value, traceback
- )
-
- async def cleanup(self) -> None:
- if self._tempdir is not None:
- await to_thread.run_sync(self._tempdir.cleanup)
-
-
-@overload
-async def mkstemp(
- suffix: str | None = None,
- prefix: str | None = None,
- dir: str | None = None,
- text: bool = False,
-) -> tuple[int, str]: ...
-
-
-@overload
-async def mkstemp(
- suffix: bytes | None = None,
- prefix: bytes | None = None,
- dir: bytes | None = None,
- text: bool = False,
-) -> tuple[int, bytes]: ...
-
-
-async def mkstemp(
- suffix: AnyStr | None = None,
- prefix: AnyStr | None = None,
- dir: AnyStr | None = None,
- text: bool = False,
-) -> tuple[int, str | bytes]:
- """
- Asynchronously create a temporary file and return an OS-level handle and the file
- name.
-
- This function wraps `tempfile.mkstemp` and executes it in a background thread.
-
- :param suffix: Suffix to be added to the file name.
- :param prefix: Prefix to be added to the file name.
- :param dir: Directory in which the temporary file is created.
- :param text: Whether the file is opened in text mode.
- :return: A tuple containing the file descriptor and the file name.
-
- """
- return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text)
-
-
-@overload
-async def mkdtemp(
- suffix: str | None = None,
- prefix: str | None = None,
- dir: str | None = None,
-) -> str: ...
-
-
-@overload
-async def mkdtemp(
- suffix: bytes | None = None,
- prefix: bytes | None = None,
- dir: bytes | None = None,
-) -> bytes: ...
-
-
-async def mkdtemp(
- suffix: AnyStr | None = None,
- prefix: AnyStr | None = None,
- dir: AnyStr | None = None,
-) -> str | bytes:
- """
- Asynchronously create a temporary directory and return its path.
-
- This function wraps `tempfile.mkdtemp` and executes it in a background thread.
-
- :param suffix: Suffix to be added to the directory name.
- :param prefix: Prefix to be added to the directory name.
- :param dir: Parent directory where the temporary directory is created.
- :return: The path of the created temporary directory.
-
- """
- return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir)
-
-
-async def gettempdir() -> str:
- """
- Asynchronously return the name of the directory used for temporary files.
-
- This function wraps `tempfile.gettempdir` and executes it in a background thread.
-
- :return: The path of the temporary directory as a string.
-
- """
- return await to_thread.run_sync(tempfile.gettempdir)
-
-
-async def gettempdirb() -> bytes:
- """
- Asynchronously return the name of the directory used for temporary files in bytes.
-
- This function wraps `tempfile.gettempdirb` and executes it in a background thread.
-
- :return: The path of the temporary directory as bytes.
-
- """
- return await to_thread.run_sync(tempfile.gettempdirb)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_testing.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_testing.py
deleted file mode 100644
index 369e65c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_testing.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import Awaitable, Generator
-from typing import Any, cast
-
-from ._eventloop import get_async_backend
-
-
-class TaskInfo:
- """
- Represents an asynchronous task.
-
- :ivar int id: the unique identifier of the task
- :ivar parent_id: the identifier of the parent task, if any
- :vartype parent_id: Optional[int]
- :ivar str name: the description of the task (if any)
- :ivar ~collections.abc.Coroutine coro: the coroutine object of the task
- """
-
- __slots__ = "_name", "id", "parent_id", "name", "coro"
-
- def __init__(
- self,
- id: int,
- parent_id: int | None,
- name: str | None,
- coro: Generator[Any, Any, Any] | Awaitable[Any],
- ):
- func = get_current_task
- self._name = f"{func.__module__}.{func.__qualname__}"
- self.id: int = id
- self.parent_id: int | None = parent_id
- self.name: str | None = name
- self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, TaskInfo):
- return self.id == other.id
-
- return NotImplemented
-
- def __hash__(self) -> int:
- return hash(self.id)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})"
-
- def has_pending_cancellation(self) -> bool:
- """
- Return ``True`` if the task has a cancellation pending, ``False`` otherwise.
-
- """
- return False
-
-
-def get_current_task() -> TaskInfo:
- """
- Return the current task.
-
- :return: a representation of the current task
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().get_current_task()
-
-
-def get_running_tasks() -> list[TaskInfo]:
- """
- Return a list of running tasks in the current event loop.
-
- :return: a list of task info objects
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return cast("list[TaskInfo]", get_async_backend().get_running_tasks())
-
-
-async def wait_all_tasks_blocked() -> None:
- """Wait until all other tasks are waiting for something."""
- await get_async_backend().wait_all_tasks_blocked()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py b/backend/.venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py
deleted file mode 100644
index f358a44..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import Callable, Mapping
-from typing import Any, TypeVar, final, overload
-
-from ._exceptions import TypedAttributeLookupError
-
-T_Attr = TypeVar("T_Attr")
-T_Default = TypeVar("T_Default")
-undefined = object()
-
-
-def typed_attribute() -> Any:
- """Return a unique object, used to mark typed attributes."""
- return object()
-
-
-class TypedAttributeSet:
- """
- Superclass for typed attribute collections.
-
- Checks that every public attribute of every subclass has a type annotation.
- """
-
- def __init_subclass__(cls) -> None:
- annotations: dict[str, Any] = getattr(cls, "__annotations__", {})
- for attrname in dir(cls):
- if not attrname.startswith("_") and attrname not in annotations:
- raise TypeError(
- f"Attribute {attrname!r} is missing its type annotation"
- )
-
- super().__init_subclass__()
-
-
-class TypedAttributeProvider:
- """Base class for classes that wish to provide typed extra attributes."""
-
- @property
- def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]:
- """
- A mapping of the extra attributes to callables that return the corresponding
- values.
-
- If the provider wraps another provider, the attributes from that wrapper should
- also be included in the returned mapping (but the wrapper may override the
- callables from the wrapped instance).
-
- """
- return {}
-
- @overload
- def extra(self, attribute: T_Attr) -> T_Attr: ...
-
- @overload
- def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ...
-
- @final
- def extra(self, attribute: Any, default: object = undefined) -> object:
- """
- extra(attribute, default=undefined)
-
- Return the value of the given typed extra attribute.
-
- :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to
- look for
- :param default: the value that should be returned if no value is found for the
- attribute
- :raises ~anyio.TypedAttributeLookupError: if the search failed and no default
- value was given
-
- """
- try:
- getter = self.extra_attributes[attribute]
- except KeyError:
- if default is undefined:
- raise TypedAttributeLookupError("Attribute not found") from None
- else:
- return default
-
- return getter()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__init__.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__init__.py
deleted file mode 100644
index d560ce3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__init__.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from __future__ import annotations
-
-from ._eventloop import AsyncBackend as AsyncBackend
-from ._resources import AsyncResource as AsyncResource
-from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket
-from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket
-from ._sockets import IPAddressType as IPAddressType
-from ._sockets import IPSockAddrType as IPSockAddrType
-from ._sockets import SocketAttribute as SocketAttribute
-from ._sockets import SocketListener as SocketListener
-from ._sockets import SocketStream as SocketStream
-from ._sockets import UDPPacketType as UDPPacketType
-from ._sockets import UDPSocket as UDPSocket
-from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType
-from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket
-from ._sockets import UNIXSocketStream as UNIXSocketStream
-from ._streams import AnyByteReceiveStream as AnyByteReceiveStream
-from ._streams import AnyByteSendStream as AnyByteSendStream
-from ._streams import AnyByteStream as AnyByteStream
-from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable
-from ._streams import AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream
-from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream
-from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream
-from ._streams import ByteReceiveStream as ByteReceiveStream
-from ._streams import ByteSendStream as ByteSendStream
-from ._streams import ByteStream as ByteStream
-from ._streams import ByteStreamConnectable as ByteStreamConnectable
-from ._streams import Listener as Listener
-from ._streams import ObjectReceiveStream as ObjectReceiveStream
-from ._streams import ObjectSendStream as ObjectSendStream
-from ._streams import ObjectStream as ObjectStream
-from ._streams import ObjectStreamConnectable as ObjectStreamConnectable
-from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream
-from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream
-from ._streams import UnreliableObjectStream as UnreliableObjectStream
-from ._subprocesses import Process as Process
-from ._tasks import TaskGroup as TaskGroup
-from ._tasks import TaskStatus as TaskStatus
-from ._testing import TestRunner as TestRunner
-
-# Re-exported here, for backwards compatibility
-# isort: off
-from .._core._synchronization import (
- CapacityLimiter as CapacityLimiter,
- Condition as Condition,
- Event as Event,
- Lock as Lock,
- Semaphore as Semaphore,
-)
-from .._core._tasks import CancelScope as CancelScope
-from ..from_thread import BlockingPortal as BlockingPortal
-
-# Re-export imports so they look like they live directly in this package
-for __value in list(locals().values()):
- if getattr(__value, "__module__", "").startswith("anyio.abc."):
- __value.__module__ = __name__
-
-del __value
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 361458e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312.pyc
deleted file mode 100644
index 454c6d3..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc
deleted file mode 100644
index 0d612b7..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc
deleted file mode 100644
index b146695..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc
deleted file mode 100644
index a5850bd..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc
deleted file mode 100644
index d5958e4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc
deleted file mode 100644
index e7bb7e4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc
deleted file mode 100644
index 7cd7d2d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_eventloop.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_eventloop.py
deleted file mode 100644
index b1bd085..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_eventloop.py
+++ /dev/null
@@ -1,414 +0,0 @@
-from __future__ import annotations
-
-import math
-import sys
-from abc import ABCMeta, abstractmethod
-from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
-from contextlib import AbstractContextManager
-from os import PathLike
-from signal import Signals
-from socket import AddressFamily, SocketKind, socket
-from typing import (
- IO,
- TYPE_CHECKING,
- Any,
- TypeVar,
- Union,
- overload,
-)
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-if sys.version_info >= (3, 10):
- from typing import TypeAlias
-else:
- from typing_extensions import TypeAlias
-
-if TYPE_CHECKING:
- from _typeshed import FileDescriptorLike
-
- from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore
- from .._core._tasks import CancelScope
- from .._core._testing import TaskInfo
- from ._sockets import (
- ConnectedUDPSocket,
- ConnectedUNIXDatagramSocket,
- IPSockAddrType,
- SocketListener,
- SocketStream,
- UDPSocket,
- UNIXDatagramSocket,
- UNIXSocketStream,
- )
- from ._subprocesses import Process
- from ._tasks import TaskGroup
- from ._testing import TestRunner
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"]
-
-
-class AsyncBackend(metaclass=ABCMeta):
- @classmethod
- @abstractmethod
- def run(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- kwargs: dict[str, Any],
- options: dict[str, Any],
- ) -> T_Retval:
- """
- Run the given coroutine function in an asynchronous event loop.
-
- The current thread must not be already running an event loop.
-
- :param func: a coroutine function
- :param args: positional arguments to ``func``
- :param kwargs: positional arguments to ``func``
- :param options: keyword arguments to call the backend ``run()`` implementation
- with
- :return: the return value of the coroutine function
- """
-
- @classmethod
- @abstractmethod
- def current_token(cls) -> object:
- """
- Return an object that allows other threads to run code inside the event loop.
-
- :return: a token object, specific to the event loop running in the current
- thread
- """
-
- @classmethod
- @abstractmethod
- def current_time(cls) -> float:
- """
- Return the current value of the event loop's internal clock.
-
- :return: the clock value (seconds)
- """
-
- @classmethod
- @abstractmethod
- def cancelled_exception_class(cls) -> type[BaseException]:
- """Return the exception class that is raised in a task if it's cancelled."""
-
- @classmethod
- @abstractmethod
- async def checkpoint(cls) -> None:
- """
- Check if the task has been cancelled, and allow rescheduling of other tasks.
-
- This is effectively the same as running :meth:`checkpoint_if_cancelled` and then
- :meth:`cancel_shielded_checkpoint`.
- """
-
- @classmethod
- async def checkpoint_if_cancelled(cls) -> None:
- """
- Check if the current task group has been cancelled.
-
- This will check if the task has been cancelled, but will not allow other tasks
- to be scheduled if not.
-
- """
- if cls.current_effective_deadline() == -math.inf:
- await cls.checkpoint()
-
- @classmethod
- async def cancel_shielded_checkpoint(cls) -> None:
- """
- Allow the rescheduling of other tasks.
-
- This will give other tasks the opportunity to run, but without checking if the
- current task group has been cancelled, unlike with :meth:`checkpoint`.
-
- """
- with cls.create_cancel_scope(shield=True):
- await cls.sleep(0)
-
- @classmethod
- @abstractmethod
- async def sleep(cls, delay: float) -> None:
- """
- Pause the current task for the specified duration.
-
- :param delay: the duration, in seconds
- """
-
- @classmethod
- @abstractmethod
- def create_cancel_scope(
- cls, *, deadline: float = math.inf, shield: bool = False
- ) -> CancelScope:
- pass
-
- @classmethod
- @abstractmethod
- def current_effective_deadline(cls) -> float:
- """
- Return the nearest deadline among all the cancel scopes effective for the
- current task.
-
- :return:
- - a clock value from the event loop's internal clock
- - ``inf`` if there is no deadline in effect
- - ``-inf`` if the current scope has been cancelled
- :rtype: float
- """
-
- @classmethod
- @abstractmethod
- def create_task_group(cls) -> TaskGroup:
- pass
-
- @classmethod
- @abstractmethod
- def create_event(cls) -> Event:
- pass
-
- @classmethod
- @abstractmethod
- def create_lock(cls, *, fast_acquire: bool) -> Lock:
- pass
-
- @classmethod
- @abstractmethod
- def create_semaphore(
- cls,
- initial_value: int,
- *,
- max_value: int | None = None,
- fast_acquire: bool = False,
- ) -> Semaphore:
- pass
-
- @classmethod
- @abstractmethod
- def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter:
- pass
-
- @classmethod
- @abstractmethod
- async def run_sync_in_worker_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- abandon_on_cancel: bool = False,
- limiter: CapacityLimiter | None = None,
- ) -> T_Retval:
- pass
-
- @classmethod
- @abstractmethod
- def check_cancelled(cls) -> None:
- pass
-
- @classmethod
- @abstractmethod
- def run_async_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- pass
-
- @classmethod
- @abstractmethod
- def run_sync_from_thread(
- cls,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- args: tuple[Unpack[PosArgsT]],
- token: object,
- ) -> T_Retval:
- pass
-
- @classmethod
- @abstractmethod
- async def open_process(
- cls,
- command: StrOrBytesPath | Sequence[StrOrBytesPath],
- *,
- stdin: int | IO[Any] | None,
- stdout: int | IO[Any] | None,
- stderr: int | IO[Any] | None,
- **kwargs: Any,
- ) -> Process:
- pass
-
- @classmethod
- @abstractmethod
- def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None:
- pass
-
- @classmethod
- @abstractmethod
- async def connect_tcp(
- cls, host: str, port: int, local_address: IPSockAddrType | None = None
- ) -> SocketStream:
- pass
-
- @classmethod
- @abstractmethod
- async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream:
- pass
-
- @classmethod
- @abstractmethod
- def create_tcp_listener(cls, sock: socket) -> SocketListener:
- pass
-
- @classmethod
- @abstractmethod
- def create_unix_listener(cls, sock: socket) -> SocketListener:
- pass
-
- @classmethod
- @abstractmethod
- async def create_udp_socket(
- cls,
- family: AddressFamily,
- local_address: IPSockAddrType | None,
- remote_address: IPSockAddrType | None,
- reuse_port: bool,
- ) -> UDPSocket | ConnectedUDPSocket:
- pass
-
- @classmethod
- @overload
- async def create_unix_datagram_socket(
- cls, raw_socket: socket, remote_path: None
- ) -> UNIXDatagramSocket: ...
-
- @classmethod
- @overload
- async def create_unix_datagram_socket(
- cls, raw_socket: socket, remote_path: str | bytes
- ) -> ConnectedUNIXDatagramSocket: ...
-
- @classmethod
- @abstractmethod
- async def create_unix_datagram_socket(
- cls, raw_socket: socket, remote_path: str | bytes | None
- ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket:
- pass
-
- @classmethod
- @abstractmethod
- async def getaddrinfo(
- cls,
- host: bytes | str | None,
- port: str | int | None,
- *,
- family: int | AddressFamily = 0,
- type: int | SocketKind = 0,
- proto: int = 0,
- flags: int = 0,
- ) -> Sequence[
- tuple[
- AddressFamily,
- SocketKind,
- int,
- str,
- tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
- ]
- ]:
- pass
-
- @classmethod
- @abstractmethod
- async def getnameinfo(
- cls, sockaddr: IPSockAddrType, flags: int = 0
- ) -> tuple[str, str]:
- pass
-
- @classmethod
- @abstractmethod
- async def wait_readable(cls, obj: FileDescriptorLike) -> None:
- pass
-
- @classmethod
- @abstractmethod
- async def wait_writable(cls, obj: FileDescriptorLike) -> None:
- pass
-
- @classmethod
- @abstractmethod
- def notify_closing(cls, obj: FileDescriptorLike) -> None:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_listener_socket(cls, sock: socket) -> SocketListener:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_stream_socket(cls, sock: socket) -> SocketStream:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_udp_socket(cls, sock: socket) -> UDPSocket:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket:
- pass
-
- @classmethod
- @abstractmethod
- async def wrap_connected_unix_datagram_socket(
- cls, sock: socket
- ) -> ConnectedUNIXDatagramSocket:
- pass
-
- @classmethod
- @abstractmethod
- def current_default_thread_limiter(cls) -> CapacityLimiter:
- pass
-
- @classmethod
- @abstractmethod
- def open_signal_receiver(
- cls, *signals: Signals
- ) -> AbstractContextManager[AsyncIterator[Signals]]:
- pass
-
- @classmethod
- @abstractmethod
- def get_current_task(cls) -> TaskInfo:
- pass
-
- @classmethod
- @abstractmethod
- def get_running_tasks(cls) -> Sequence[TaskInfo]:
- pass
-
- @classmethod
- @abstractmethod
- async def wait_all_tasks_blocked(cls) -> None:
- pass
-
- @classmethod
- @abstractmethod
- def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
- pass
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_resources.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_resources.py
deleted file mode 100644
index 10df115..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_resources.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from __future__ import annotations
-
-from abc import ABCMeta, abstractmethod
-from types import TracebackType
-from typing import TypeVar
-
-T = TypeVar("T")
-
-
-class AsyncResource(metaclass=ABCMeta):
- """
- Abstract base class for all closeable asynchronous resources.
-
- Works as an asynchronous context manager which returns the instance itself on enter,
- and calls :meth:`aclose` on exit.
- """
-
- __slots__ = ()
-
- async def __aenter__(self: T) -> T:
- return self
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- await self.aclose()
-
- @abstractmethod
- async def aclose(self) -> None:
- """Close the resource."""
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_sockets.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_sockets.py
deleted file mode 100644
index 3ff60d4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_sockets.py
+++ /dev/null
@@ -1,405 +0,0 @@
-from __future__ import annotations
-
-import errno
-import socket
-import sys
-from abc import abstractmethod
-from collections.abc import Callable, Collection, Mapping
-from contextlib import AsyncExitStack
-from io import IOBase
-from ipaddress import IPv4Address, IPv6Address
-from socket import AddressFamily
-from typing import Any, TypeVar, Union
-
-from .._core._eventloop import get_async_backend
-from .._core._typedattr import (
- TypedAttributeProvider,
- TypedAttributeSet,
- typed_attribute,
-)
-from ._streams import ByteStream, Listener, UnreliableObjectStream
-from ._tasks import TaskGroup
-
-if sys.version_info >= (3, 10):
- from typing import TypeAlias
-else:
- from typing_extensions import TypeAlias
-
-IPAddressType: TypeAlias = Union[str, IPv4Address, IPv6Address]
-IPSockAddrType: TypeAlias = tuple[str, int]
-SockAddrType: TypeAlias = Union[IPSockAddrType, str]
-UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType]
-UNIXDatagramPacketType: TypeAlias = tuple[bytes, str]
-T_Retval = TypeVar("T_Retval")
-
-
-def _validate_socket(
- sock_or_fd: socket.socket | int,
- sock_type: socket.SocketKind,
- addr_family: socket.AddressFamily = socket.AF_UNSPEC,
- *,
- require_connected: bool = False,
- require_bound: bool = False,
-) -> socket.socket:
- if isinstance(sock_or_fd, int):
- try:
- sock = socket.socket(fileno=sock_or_fd)
- except OSError as exc:
- if exc.errno == errno.ENOTSOCK:
- raise ValueError(
- "the file descriptor does not refer to a socket"
- ) from exc
- elif require_connected:
- raise ValueError("the socket must be connected") from exc
- elif require_bound:
- raise ValueError("the socket must be bound to a local address") from exc
- else:
- raise
- elif isinstance(sock_or_fd, socket.socket):
- sock = sock_or_fd
- else:
- raise TypeError(
- f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead"
- )
-
- try:
- if require_connected:
- try:
- sock.getpeername()
- except OSError as exc:
- raise ValueError("the socket must be connected") from exc
-
- if require_bound:
- try:
- if sock.family in (socket.AF_INET, socket.AF_INET6):
- bound_addr = sock.getsockname()[1]
- else:
- bound_addr = sock.getsockname()
- except OSError:
- bound_addr = None
-
- if not bound_addr:
- raise ValueError("the socket must be bound to a local address")
-
- if addr_family != socket.AF_UNSPEC and sock.family != addr_family:
- raise ValueError(
- f"address family mismatch: expected {addr_family.name}, got "
- f"{sock.family.name}"
- )
-
- if sock.type != sock_type:
- raise ValueError(
- f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}"
- )
- except BaseException:
- # Avoid ResourceWarning from the locally constructed socket object
- if isinstance(sock_or_fd, int):
- sock.detach()
-
- raise
-
- sock.setblocking(False)
- return sock
-
-
-class SocketAttribute(TypedAttributeSet):
- """
- .. attribute:: family
- :type: socket.AddressFamily
-
- the address family of the underlying socket
-
- .. attribute:: local_address
- :type: tuple[str, int] | str
-
- the local address the underlying socket is connected to
-
- .. attribute:: local_port
- :type: int
-
- for IP based sockets, the local port the underlying socket is bound to
-
- .. attribute:: raw_socket
- :type: socket.socket
-
- the underlying stdlib socket object
-
- .. attribute:: remote_address
- :type: tuple[str, int] | str
-
- the remote address the underlying socket is connected to
-
- .. attribute:: remote_port
- :type: int
-
- for IP based sockets, the remote port the underlying socket is connected to
- """
-
- family: AddressFamily = typed_attribute()
- local_address: SockAddrType = typed_attribute()
- local_port: int = typed_attribute()
- raw_socket: socket.socket = typed_attribute()
- remote_address: SockAddrType = typed_attribute()
- remote_port: int = typed_attribute()
-
-
-class _SocketProvider(TypedAttributeProvider):
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- from .._core._sockets import convert_ipv6_sockaddr as convert
-
- attributes: dict[Any, Callable[[], Any]] = {
- SocketAttribute.family: lambda: self._raw_socket.family,
- SocketAttribute.local_address: lambda: convert(
- self._raw_socket.getsockname()
- ),
- SocketAttribute.raw_socket: lambda: self._raw_socket,
- }
- try:
- peername: tuple[str, int] | None = convert(self._raw_socket.getpeername())
- except OSError:
- peername = None
-
- # Provide the remote address for connected sockets
- if peername is not None:
- attributes[SocketAttribute.remote_address] = lambda: peername
-
- # Provide local and remote ports for IP based sockets
- if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6):
- attributes[SocketAttribute.local_port] = (
- lambda: self._raw_socket.getsockname()[1]
- )
- if peername is not None:
- remote_port = peername[1]
- attributes[SocketAttribute.remote_port] = lambda: remote_port
-
- return attributes
-
- @property
- @abstractmethod
- def _raw_socket(self) -> socket.socket:
- pass
-
-
-class SocketStream(ByteStream, _SocketProvider):
- """
- Transports bytes over a socket.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream:
- """
- Wrap an existing socket object or file descriptor as a socket stream.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
- The existing socket must already be connected.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a socket stream
-
- """
- sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True)
- return await get_async_backend().wrap_stream_socket(sock)
-
-
-class UNIXSocketStream(SocketStream):
- @classmethod
- async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream:
- """
- Wrap an existing socket object or file descriptor as a UNIX socket stream.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
- The existing socket must already be connected.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a UNIX socket stream
-
- """
- sock = _validate_socket(
- sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True
- )
- return await get_async_backend().wrap_unix_stream_socket(sock)
-
- @abstractmethod
- async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
- """
- Send file descriptors along with a message to the peer.
-
- :param message: a non-empty bytestring
- :param fds: a collection of files (either numeric file descriptors or open file
- or socket objects)
- """
-
- @abstractmethod
- async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
- """
- Receive file descriptors along with a message from the peer.
-
- :param msglen: length of the message to expect from the peer
- :param maxfds: maximum number of file descriptors to expect from the peer
- :return: a tuple of (message, file descriptors)
- """
-
-
-class SocketListener(Listener[SocketStream], _SocketProvider):
- """
- Listens to incoming socket connections.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(
- cls,
- sock_or_fd: socket.socket | int,
- ) -> SocketListener:
- """
- Wrap an existing socket object or file descriptor as a socket listener.
-
- The newly created listener takes ownership of the socket being passed in.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a socket listener
-
- """
- sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True)
- return await get_async_backend().wrap_listener_socket(sock)
-
- @abstractmethod
- async def accept(self) -> SocketStream:
- """Accept an incoming connection."""
-
- async def serve(
- self,
- handler: Callable[[SocketStream], Any],
- task_group: TaskGroup | None = None,
- ) -> None:
- from .. import create_task_group
-
- async with AsyncExitStack() as stack:
- if task_group is None:
- task_group = await stack.enter_async_context(create_task_group())
-
- while True:
- stream = await self.accept()
- task_group.start_soon(handler, stream)
-
-
-class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider):
- """
- Represents an unconnected UDP socket.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket:
- """
- Wrap an existing socket object or file descriptor as a UDP socket.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
- The existing socket must be bound to a local address.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a UDP socket
-
- """
- sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True)
- return await get_async_backend().wrap_udp_socket(sock)
-
- async def sendto(self, data: bytes, host: str, port: int) -> None:
- """
- Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))).
-
- """
- return await self.send((data, (host, port)))
-
-
-class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider):
- """
- Represents an connected UDP socket.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket:
- """
- Wrap an existing socket object or file descriptor as a connected UDP socket.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
- The existing socket must already be connected.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a connected UDP socket
-
- """
- sock = _validate_socket(
- sock_or_fd,
- socket.SOCK_DGRAM,
- require_connected=True,
- )
- return await get_async_backend().wrap_connected_udp_socket(sock)
-
-
-class UNIXDatagramSocket(
- UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider
-):
- """
- Represents an unconnected Unix datagram socket.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(
- cls,
- sock_or_fd: socket.socket | int,
- ) -> UNIXDatagramSocket:
- """
- Wrap an existing socket object or file descriptor as a UNIX datagram
- socket.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a UNIX datagram socket
-
- """
- sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX)
- return await get_async_backend().wrap_unix_datagram_socket(sock)
-
- async def sendto(self, data: bytes, path: str) -> None:
- """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path))."""
- return await self.send((data, path))
-
-
-class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider):
- """
- Represents a connected Unix datagram socket.
-
- Supports all relevant extra attributes from :class:`~SocketAttribute`.
- """
-
- @classmethod
- async def from_socket(
- cls,
- sock_or_fd: socket.socket | int,
- ) -> ConnectedUNIXDatagramSocket:
- """
- Wrap an existing socket object or file descriptor as a connected UNIX datagram
- socket.
-
- The newly created socket wrapper takes ownership of the socket being passed in.
- The existing socket must already be connected.
-
- :param sock_or_fd: a socket object or file descriptor
- :return: a connected UNIX datagram socket
-
- """
- sock = _validate_socket(
- sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True
- )
- return await get_async_backend().wrap_connected_unix_datagram_socket(sock)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_streams.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_streams.py
deleted file mode 100644
index 369df3f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_streams.py
+++ /dev/null
@@ -1,239 +0,0 @@
-from __future__ import annotations
-
-import sys
-from abc import ABCMeta, abstractmethod
-from collections.abc import Callable
-from typing import Any, Generic, TypeVar, Union
-
-from .._core._exceptions import EndOfStream
-from .._core._typedattr import TypedAttributeProvider
-from ._resources import AsyncResource
-from ._tasks import TaskGroup
-
-if sys.version_info >= (3, 10):
- from typing import TypeAlias
-else:
- from typing_extensions import TypeAlias
-
-T_Item = TypeVar("T_Item")
-T_co = TypeVar("T_co", covariant=True)
-T_contra = TypeVar("T_contra", contravariant=True)
-
-
-class UnreliableObjectReceiveStream(
- Generic[T_co], AsyncResource, TypedAttributeProvider
-):
- """
- An interface for receiving objects.
-
- This interface makes no guarantees that the received messages arrive in the order in
- which they were sent, or that no messages are missed.
-
- Asynchronously iterating over objects of this type will yield objects matching the
- given type parameter.
- """
-
- def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]:
- return self
-
- async def __anext__(self) -> T_co:
- try:
- return await self.receive()
- except EndOfStream:
- raise StopAsyncIteration from None
-
- @abstractmethod
- async def receive(self) -> T_co:
- """
- Receive the next item.
-
- :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly
- closed
- :raises ~anyio.EndOfStream: if this stream has been closed from the other end
- :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable
- due to external causes
- """
-
-
-class UnreliableObjectSendStream(
- Generic[T_contra], AsyncResource, TypedAttributeProvider
-):
- """
- An interface for sending objects.
-
- This interface makes no guarantees that the messages sent will reach the
- recipient(s) in the same order in which they were sent, or at all.
- """
-
- @abstractmethod
- async def send(self, item: T_contra) -> None:
- """
- Send an item to the peer(s).
-
- :param item: the item to send
- :raises ~anyio.ClosedResourceError: if the send stream has been explicitly
- closed
- :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable
- due to external causes
- """
-
-
-class UnreliableObjectStream(
- UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item]
-):
- """
- A bidirectional message stream which does not guarantee the order or reliability of
- message delivery.
- """
-
-
-class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]):
- """
- A receive message stream which guarantees that messages are received in the same
- order in which they were sent, and that no messages are missed.
- """
-
-
-class ObjectSendStream(UnreliableObjectSendStream[T_contra]):
- """
- A send message stream which guarantees that messages are delivered in the same order
- in which they were sent, without missing any messages in the middle.
- """
-
-
-class ObjectStream(
- ObjectReceiveStream[T_Item],
- ObjectSendStream[T_Item],
- UnreliableObjectStream[T_Item],
-):
- """
- A bidirectional message stream which guarantees the order and reliability of message
- delivery.
- """
-
- @abstractmethod
- async def send_eof(self) -> None:
- """
- Send an end-of-file indication to the peer.
-
- You should not try to send any further data to this stream after calling this
- method. This method is idempotent (does nothing on successive calls).
- """
-
-
-class ByteReceiveStream(AsyncResource, TypedAttributeProvider):
- """
- An interface for receiving bytes from a single peer.
-
- Iterating this byte stream will yield a byte string of arbitrary length, but no more
- than 65536 bytes.
- """
-
- def __aiter__(self) -> ByteReceiveStream:
- return self
-
- async def __anext__(self) -> bytes:
- try:
- return await self.receive()
- except EndOfStream:
- raise StopAsyncIteration from None
-
- @abstractmethod
- async def receive(self, max_bytes: int = 65536) -> bytes:
- """
- Receive at most ``max_bytes`` bytes from the peer.
-
- .. note:: Implementers of this interface should not return an empty
- :class:`bytes` object, and users should ignore them.
-
- :param max_bytes: maximum number of bytes to receive
- :return: the received bytes
- :raises ~anyio.EndOfStream: if this stream has been closed from the other end
- """
-
-
-class ByteSendStream(AsyncResource, TypedAttributeProvider):
- """An interface for sending bytes to a single peer."""
-
- @abstractmethod
- async def send(self, item: bytes) -> None:
- """
- Send the given bytes to the peer.
-
- :param item: the bytes to send
- """
-
-
-class ByteStream(ByteReceiveStream, ByteSendStream):
- """A bidirectional byte stream."""
-
- @abstractmethod
- async def send_eof(self) -> None:
- """
- Send an end-of-file indication to the peer.
-
- You should not try to send any further data to this stream after calling this
- method. This method is idempotent (does nothing on successive calls).
- """
-
-
-#: Type alias for all unreliable bytes-oriented receive streams.
-AnyUnreliableByteReceiveStream: TypeAlias = Union[
- UnreliableObjectReceiveStream[bytes], ByteReceiveStream
-]
-#: Type alias for all unreliable bytes-oriented send streams.
-AnyUnreliableByteSendStream: TypeAlias = Union[
- UnreliableObjectSendStream[bytes], ByteSendStream
-]
-#: Type alias for all unreliable bytes-oriented streams.
-AnyUnreliableByteStream: TypeAlias = Union[UnreliableObjectStream[bytes], ByteStream]
-#: Type alias for all bytes-oriented receive streams.
-AnyByteReceiveStream: TypeAlias = Union[ObjectReceiveStream[bytes], ByteReceiveStream]
-#: Type alias for all bytes-oriented send streams.
-AnyByteSendStream: TypeAlias = Union[ObjectSendStream[bytes], ByteSendStream]
-#: Type alias for all bytes-oriented streams.
-AnyByteStream: TypeAlias = Union[ObjectStream[bytes], ByteStream]
-
-
-class Listener(Generic[T_co], AsyncResource, TypedAttributeProvider):
- """An interface for objects that let you accept incoming connections."""
-
- @abstractmethod
- async def serve(
- self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None
- ) -> None:
- """
- Accept incoming connections as they come in and start tasks to handle them.
-
- :param handler: a callable that will be used to handle each accepted connection
- :param task_group: the task group that will be used to start tasks for handling
- each accepted connection (if omitted, an ad-hoc task group will be created)
- """
-
-
-class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta):
- @abstractmethod
- async def connect(self) -> ObjectStream[T_co]:
- """
- Connect to the remote endpoint.
-
- :return: an object stream connected to the remote end
- :raises ConnectionFailed: if the connection fails
- """
-
-
-class ByteStreamConnectable(metaclass=ABCMeta):
- @abstractmethod
- async def connect(self) -> ByteStream:
- """
- Connect to the remote endpoint.
-
- :return: a bytestream connected to the remote end
- :raises ConnectionFailed: if the connection fails
- """
-
-
-#: Type alias for all connectables returning bytestreams or bytes-oriented object streams
-AnyByteStreamConnectable: TypeAlias = Union[
- ObjectStreamConnectable[bytes], ByteStreamConnectable
-]
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py
deleted file mode 100644
index ce0564c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py
+++ /dev/null
@@ -1,79 +0,0 @@
-from __future__ import annotations
-
-from abc import abstractmethod
-from signal import Signals
-
-from ._resources import AsyncResource
-from ._streams import ByteReceiveStream, ByteSendStream
-
-
-class Process(AsyncResource):
- """An asynchronous version of :class:`subprocess.Popen`."""
-
- @abstractmethod
- async def wait(self) -> int:
- """
- Wait until the process exits.
-
- :return: the exit code of the process
- """
-
- @abstractmethod
- def terminate(self) -> None:
- """
- Terminates the process, gracefully if possible.
-
- On Windows, this calls ``TerminateProcess()``.
- On POSIX systems, this sends ``SIGTERM`` to the process.
-
- .. seealso:: :meth:`subprocess.Popen.terminate`
- """
-
- @abstractmethod
- def kill(self) -> None:
- """
- Kills the process.
-
- On Windows, this calls ``TerminateProcess()``.
- On POSIX systems, this sends ``SIGKILL`` to the process.
-
- .. seealso:: :meth:`subprocess.Popen.kill`
- """
-
- @abstractmethod
- def send_signal(self, signal: Signals) -> None:
- """
- Send a signal to the subprocess.
-
- .. seealso:: :meth:`subprocess.Popen.send_signal`
-
- :param signal: the signal number (e.g. :data:`signal.SIGHUP`)
- """
-
- @property
- @abstractmethod
- def pid(self) -> int:
- """The process ID of the process."""
-
- @property
- @abstractmethod
- def returncode(self) -> int | None:
- """
- The return code of the process. If the process has not yet terminated, this will
- be ``None``.
- """
-
- @property
- @abstractmethod
- def stdin(self) -> ByteSendStream | None:
- """The stream for the standard input of the process."""
-
- @property
- @abstractmethod
- def stdout(self) -> ByteReceiveStream | None:
- """The stream for the standard output of the process."""
-
- @property
- @abstractmethod
- def stderr(self) -> ByteReceiveStream | None:
- """The stream for the standard error output of the process."""
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_tasks.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_tasks.py
deleted file mode 100644
index 516b3ec..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_tasks.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from __future__ import annotations
-
-import sys
-from abc import ABCMeta, abstractmethod
-from collections.abc import Awaitable, Callable
-from types import TracebackType
-from typing import TYPE_CHECKING, Any, Protocol, overload
-
-if sys.version_info >= (3, 13):
- from typing import TypeVar
-else:
- from typing_extensions import TypeVar
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-if TYPE_CHECKING:
- from .._core._tasks import CancelScope
-
-T_Retval = TypeVar("T_Retval")
-T_contra = TypeVar("T_contra", contravariant=True, default=None)
-PosArgsT = TypeVarTuple("PosArgsT")
-
-
-class TaskStatus(Protocol[T_contra]):
- @overload
- def started(self: TaskStatus[None]) -> None: ...
-
- @overload
- def started(self, value: T_contra) -> None: ...
-
- def started(self, value: T_contra | None = None) -> None:
- """
- Signal that the task has started.
-
- :param value: object passed back to the starter of the task
- """
-
-
-class TaskGroup(metaclass=ABCMeta):
- """
- Groups several asynchronous tasks together.
-
- :ivar cancel_scope: the cancel scope inherited by all child tasks
- :vartype cancel_scope: CancelScope
-
- .. note:: On asyncio, support for eager task factories is considered to be
- **experimental**. In particular, they don't follow the usual semantics of new
- tasks being scheduled on the next iteration of the event loop, and may thus
- cause unexpected behavior in code that wasn't written with such semantics in
- mind.
- """
-
- cancel_scope: CancelScope
-
- @abstractmethod
- def start_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[Any]],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> None:
- """
- Start a new task in this task group.
-
- :param func: a coroutine function
- :param args: positional arguments to call the function with
- :param name: name of the task, for the purposes of introspection and debugging
-
- .. versionadded:: 3.0
- """
-
- @abstractmethod
- async def start(
- self,
- func: Callable[..., Awaitable[Any]],
- *args: object,
- name: object = None,
- ) -> Any:
- """
- Start a new task and wait until it signals for readiness.
-
- The target callable must accept a keyword argument ``task_status`` (of type
- :class:`TaskStatus`). Awaiting on this method will return whatever was passed to
- ``task_status.started()`` (``None`` by default).
-
- .. note:: The :class:`TaskStatus` class is generic, and the type argument should
- indicate the type of the value that will be passed to
- ``task_status.started()``.
-
- :param func: a coroutine function that accepts the ``task_status`` keyword
- argument
- :param args: positional arguments to call the function with
- :param name: an optional name for the task, for introspection and debugging
- :return: the value passed to ``task_status.started()``
- :raises RuntimeError: if the task finishes without calling
- ``task_status.started()``
-
- .. seealso:: :ref:`start_initialize`
-
- .. versionadded:: 3.0
- """
-
- @abstractmethod
- async def __aenter__(self) -> TaskGroup:
- """Enter the task group context and allow starting new tasks."""
-
- @abstractmethod
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- """Exit the task group context waiting for all tasks to finish."""
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_testing.py b/backend/.venv/lib/python3.12/site-packages/anyio/abc/_testing.py
deleted file mode 100644
index 7c50ed7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/abc/_testing.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from __future__ import annotations
-
-import types
-from abc import ABCMeta, abstractmethod
-from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable
-from typing import Any, TypeVar
-
-_T = TypeVar("_T")
-
-
-class TestRunner(metaclass=ABCMeta):
- """
- Encapsulates a running event loop. Every call made through this object will use the
- same event loop.
- """
-
- def __enter__(self) -> TestRunner:
- return self
-
- @abstractmethod
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: types.TracebackType | None,
- ) -> bool | None: ...
-
- @abstractmethod
- def run_asyncgen_fixture(
- self,
- fixture_func: Callable[..., AsyncGenerator[_T, Any]],
- kwargs: dict[str, Any],
- ) -> Iterable[_T]:
- """
- Run an async generator fixture.
-
- :param fixture_func: the fixture function
- :param kwargs: keyword arguments to call the fixture function with
- :return: an iterator yielding the value yielded from the async generator
- """
-
- @abstractmethod
- def run_fixture(
- self,
- fixture_func: Callable[..., Coroutine[Any, Any, _T]],
- kwargs: dict[str, Any],
- ) -> _T:
- """
- Run an async fixture.
-
- :param fixture_func: the fixture function
- :param kwargs: keyword arguments to call the fixture function with
- :return: the return value of the fixture function
- """
-
- @abstractmethod
- def run_test(
- self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
- ) -> None:
- """
- Run an async test function.
-
- :param test_func: the test function
- :param kwargs: keyword arguments to call the test function with
- """
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/from_thread.py b/backend/.venv/lib/python3.12/site-packages/anyio/from_thread.py
deleted file mode 100644
index 837de5e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/from_thread.py
+++ /dev/null
@@ -1,578 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "BlockingPortal",
- "BlockingPortalProvider",
- "check_cancelled",
- "run",
- "run_sync",
- "start_blocking_portal",
-)
-
-import sys
-from collections.abc import Awaitable, Callable, Generator
-from concurrent.futures import Future
-from contextlib import (
- AbstractAsyncContextManager,
- AbstractContextManager,
- contextmanager,
-)
-from dataclasses import dataclass, field
-from functools import partial
-from inspect import isawaitable
-from threading import Lock, Thread, current_thread, get_ident
-from types import TracebackType
-from typing import (
- Any,
- Generic,
- TypeVar,
- cast,
- overload,
-)
-
-from ._core._eventloop import (
- get_cancelled_exc_class,
- threadlocals,
-)
-from ._core._eventloop import run as run_eventloop
-from ._core._exceptions import NoEventLoopError
-from ._core._synchronization import Event
-from ._core._tasks import CancelScope, create_task_group
-from .abc._tasks import TaskStatus
-from .lowlevel import EventLoopToken, current_token
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-T_Retval = TypeVar("T_Retval")
-T_co = TypeVar("T_co", covariant=True)
-PosArgsT = TypeVarTuple("PosArgsT")
-
-
-def _token_or_error(token: EventLoopToken | None) -> EventLoopToken:
- if token is not None:
- return token
-
- try:
- return threadlocals.current_token
- except AttributeError:
- raise NoEventLoopError(
- "Not running inside an AnyIO worker thread, and no event loop token was "
- "provided"
- ) from None
-
-
-def run(
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- *args: Unpack[PosArgsT],
- token: EventLoopToken | None = None,
-) -> T_Retval:
- """
- Call a coroutine function from a worker thread.
-
- :param func: a coroutine function
- :param args: positional arguments for the callable
- :param token: an event loop token to use to get back to the event loop thread
- (required if calling this function from outside an AnyIO worker thread)
- :return: the return value of the coroutine function
- :raises MissingTokenError: if no token was provided and called from outside an
- AnyIO worker thread
- :raises RunFinishedError: if the event loop tied to ``token`` is no longer running
-
- .. versionchanged:: 4.11.0
- Added the ``token`` parameter.
-
- """
- explicit_token = token is not None
- token = _token_or_error(token)
- return token.backend_class.run_async_from_thread(
- func, args, token=token.native_token if explicit_token else None
- )
-
-
-def run_sync(
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- *args: Unpack[PosArgsT],
- token: EventLoopToken | None = None,
-) -> T_Retval:
- """
- Call a function in the event loop thread from a worker thread.
-
- :param func: a callable
- :param args: positional arguments for the callable
- :param token: an event loop token to use to get back to the event loop thread
- (required if calling this function from outside an AnyIO worker thread)
- :return: the return value of the callable
- :raises MissingTokenError: if no token was provided and called from outside an
- AnyIO worker thread
- :raises RunFinishedError: if the event loop tied to ``token`` is no longer running
-
- .. versionchanged:: 4.11.0
- Added the ``token`` parameter.
-
- """
- explicit_token = token is not None
- token = _token_or_error(token)
- return token.backend_class.run_sync_from_thread(
- func, args, token=token.native_token if explicit_token else None
- )
-
-
-class _BlockingAsyncContextManager(Generic[T_co], AbstractContextManager):
- _enter_future: Future[T_co]
- _exit_future: Future[bool | None]
- _exit_event: Event
- _exit_exc_info: tuple[
- type[BaseException] | None, BaseException | None, TracebackType | None
- ] = (None, None, None)
-
- def __init__(
- self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal
- ):
- self._async_cm = async_cm
- self._portal = portal
-
- async def run_async_cm(self) -> bool | None:
- try:
- self._exit_event = Event()
- value = await self._async_cm.__aenter__()
- except BaseException as exc:
- self._enter_future.set_exception(exc)
- raise
- else:
- self._enter_future.set_result(value)
-
- try:
- # Wait for the sync context manager to exit.
- # This next statement can raise `get_cancelled_exc_class()` if
- # something went wrong in a task group in this async context
- # manager.
- await self._exit_event.wait()
- finally:
- # In case of cancellation, it could be that we end up here before
- # `_BlockingAsyncContextManager.__exit__` is called, and an
- # `_exit_exc_info` has been set.
- result = await self._async_cm.__aexit__(*self._exit_exc_info)
-
- return result
-
- def __enter__(self) -> T_co:
- self._enter_future = Future()
- self._exit_future = self._portal.start_task_soon(self.run_async_cm)
- return self._enter_future.result()
-
- def __exit__(
- self,
- __exc_type: type[BaseException] | None,
- __exc_value: BaseException | None,
- __traceback: TracebackType | None,
- ) -> bool | None:
- self._exit_exc_info = __exc_type, __exc_value, __traceback
- self._portal.call(self._exit_event.set)
- return self._exit_future.result()
-
-
-class _BlockingPortalTaskStatus(TaskStatus):
- def __init__(self, future: Future):
- self._future = future
-
- def started(self, value: object = None) -> None:
- self._future.set_result(value)
-
-
-class BlockingPortal:
- """
- An object that lets external threads run code in an asynchronous event loop.
-
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
- """
-
- def __init__(self) -> None:
- self._token = current_token()
- self._event_loop_thread_id: int | None = get_ident()
- self._stop_event = Event()
- self._task_group = create_task_group()
-
- async def __aenter__(self) -> BlockingPortal:
- await self._task_group.__aenter__()
- return self
-
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool:
- await self.stop()
- return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
-
- def _check_running(self) -> None:
- if self._event_loop_thread_id is None:
- raise RuntimeError("This portal is not running")
- if self._event_loop_thread_id == get_ident():
- raise RuntimeError(
- "This method cannot be called from the event loop thread"
- )
-
- async def sleep_until_stopped(self) -> None:
- """Sleep until :meth:`stop` is called."""
- await self._stop_event.wait()
-
- async def stop(self, cancel_remaining: bool = False) -> None:
- """
- Signal the portal to shut down.
-
- This marks the portal as no longer accepting new calls and exits from
- :meth:`sleep_until_stopped`.
-
- :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False``
- to let them finish before returning
-
- """
- self._event_loop_thread_id = None
- self._stop_event.set()
- if cancel_remaining:
- self._task_group.cancel_scope.cancel("the blocking portal is shutting down")
-
- async def _call_func(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
- args: tuple[Unpack[PosArgsT]],
- kwargs: dict[str, Any],
- future: Future[T_Retval],
- ) -> None:
- def callback(f: Future[T_Retval]) -> None:
- if f.cancelled():
- if self._event_loop_thread_id == get_ident():
- scope.cancel("the future was cancelled")
- elif self._event_loop_thread_id is not None:
- self.call(scope.cancel, "the future was cancelled")
-
- try:
- retval_or_awaitable = func(*args, **kwargs)
- if isawaitable(retval_or_awaitable):
- with CancelScope() as scope:
- future.add_done_callback(callback)
- retval = await retval_or_awaitable
- else:
- retval = retval_or_awaitable
- except get_cancelled_exc_class():
- future.cancel()
- future.set_running_or_notify_cancel()
- except BaseException as exc:
- if not future.cancelled():
- future.set_exception(exc)
-
- # Let base exceptions fall through
- if not isinstance(exc, Exception):
- raise
- else:
- if not future.cancelled():
- future.set_result(retval)
- finally:
- scope = None # type: ignore[assignment]
-
- def _spawn_task_from_thread(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
- args: tuple[Unpack[PosArgsT]],
- kwargs: dict[str, Any],
- name: object,
- future: Future[T_Retval],
- ) -> None:
- """
- Spawn a new task using the given callable.
-
- :param func: a callable
- :param args: positional arguments to be passed to the callable
- :param kwargs: keyword arguments to be passed to the callable
- :param name: name of the task (will be coerced to a string if not ``None``)
- :param future: a future that will resolve to the return value of the callable,
- or the exception raised during its execution
-
- """
- run_sync(
- partial(self._task_group.start_soon, name=name),
- self._call_func,
- func,
- args,
- kwargs,
- future,
- token=self._token,
- )
-
- @overload
- def call(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- *args: Unpack[PosArgsT],
- ) -> T_Retval: ...
-
- @overload
- def call(
- self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT]
- ) -> T_Retval: ...
-
- def call(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
- *args: Unpack[PosArgsT],
- ) -> T_Retval:
- """
- Call the given function in the event loop thread.
-
- If the callable returns a coroutine object, it is awaited on.
-
- :param func: any callable
- :raises RuntimeError: if the portal is not running or if this method is called
- from within the event loop thread
-
- """
- return cast(T_Retval, self.start_task_soon(func, *args).result())
-
- @overload
- def start_task_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> Future[T_Retval]: ...
-
- @overload
- def start_task_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> Future[T_Retval]: ...
-
- def start_task_soon(
- self,
- func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
- *args: Unpack[PosArgsT],
- name: object = None,
- ) -> Future[T_Retval]:
- """
- Start a task in the portal's task group.
-
- The task will be run inside a cancel scope which can be cancelled by cancelling
- the returned future.
-
- :param func: the target function
- :param args: positional arguments passed to ``func``
- :param name: name of the task (will be coerced to a string if not ``None``)
- :return: a future that resolves with the return value of the callable if the
- task completes successfully, or with the exception raised in the task
- :raises RuntimeError: if the portal is not running or if this method is called
- from within the event loop thread
- :rtype: concurrent.futures.Future[T_Retval]
-
- .. versionadded:: 3.0
-
- """
- self._check_running()
- f: Future[T_Retval] = Future()
- self._spawn_task_from_thread(func, args, {}, name, f)
- return f
-
- def start_task(
- self,
- func: Callable[..., Awaitable[T_Retval]],
- *args: object,
- name: object = None,
- ) -> tuple[Future[T_Retval], Any]:
- """
- Start a task in the portal's task group and wait until it signals for readiness.
-
- This method works the same way as :meth:`.abc.TaskGroup.start`.
-
- :param func: the target function
- :param args: positional arguments passed to ``func``
- :param name: name of the task (will be coerced to a string if not ``None``)
- :return: a tuple of (future, task_status_value) where the ``task_status_value``
- is the value passed to ``task_status.started()`` from within the target
- function
- :rtype: tuple[concurrent.futures.Future[T_Retval], Any]
-
- .. versionadded:: 3.0
-
- """
-
- def task_done(future: Future[T_Retval]) -> None:
- if not task_status_future.done():
- if future.cancelled():
- task_status_future.cancel()
- elif future.exception():
- task_status_future.set_exception(future.exception())
- else:
- exc = RuntimeError(
- "Task exited without calling task_status.started()"
- )
- task_status_future.set_exception(exc)
-
- self._check_running()
- task_status_future: Future = Future()
- task_status = _BlockingPortalTaskStatus(task_status_future)
- f: Future = Future()
- f.add_done_callback(task_done)
- self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f)
- return f, task_status_future.result()
-
- def wrap_async_context_manager(
- self, cm: AbstractAsyncContextManager[T_co]
- ) -> AbstractContextManager[T_co]:
- """
- Wrap an async context manager as a synchronous context manager via this portal.
-
- Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping
- in the middle until the synchronous context manager exits.
-
- :param cm: an asynchronous context manager
- :return: a synchronous context manager
-
- .. versionadded:: 2.1
-
- """
- return _BlockingAsyncContextManager(cm, self)
-
-
-@dataclass
-class BlockingPortalProvider:
- """
- A manager for a blocking portal. Used as a context manager. The first thread to
- enter this context manager causes a blocking portal to be started with the specific
- parameters, and the last thread to exit causes the portal to be shut down. Thus,
- there will be exactly one blocking portal running in this context as long as at
- least one thread has entered this context manager.
-
- The parameters are the same as for :func:`~anyio.run`.
-
- :param backend: name of the backend
- :param backend_options: backend options
-
- .. versionadded:: 4.4
- """
-
- backend: str = "asyncio"
- backend_options: dict[str, Any] | None = None
- _lock: Lock = field(init=False, default_factory=Lock)
- _leases: int = field(init=False, default=0)
- _portal: BlockingPortal = field(init=False)
- _portal_cm: AbstractContextManager[BlockingPortal] | None = field(
- init=False, default=None
- )
-
- def __enter__(self) -> BlockingPortal:
- with self._lock:
- if self._portal_cm is None:
- self._portal_cm = start_blocking_portal(
- self.backend, self.backend_options
- )
- self._portal = self._portal_cm.__enter__()
-
- self._leases += 1
- return self._portal
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- portal_cm: AbstractContextManager[BlockingPortal] | None = None
- with self._lock:
- assert self._portal_cm
- assert self._leases > 0
- self._leases -= 1
- if not self._leases:
- portal_cm = self._portal_cm
- self._portal_cm = None
- del self._portal
-
- if portal_cm:
- portal_cm.__exit__(None, None, None)
-
-
-@contextmanager
-def start_blocking_portal(
- backend: str = "asyncio",
- backend_options: dict[str, Any] | None = None,
- *,
- name: str | None = None,
-) -> Generator[BlockingPortal, Any, None]:
- """
- Start a new event loop in a new thread and run a blocking portal in its main task.
-
- The parameters are the same as for :func:`~anyio.run`.
-
- :param backend: name of the backend
- :param backend_options: backend options
- :param name: name of the thread
- :return: a context manager that yields a blocking portal
-
- .. versionchanged:: 3.0
- Usage as a context manager is now required.
-
- """
-
- async def run_portal() -> None:
- async with BlockingPortal() as portal_:
- if name is None:
- current_thread().name = f"{backend}-portal-{id(portal_):x}"
-
- future.set_result(portal_)
- await portal_.sleep_until_stopped()
-
- def run_blocking_portal() -> None:
- if future.set_running_or_notify_cancel():
- try:
- run_eventloop(
- run_portal, backend=backend, backend_options=backend_options
- )
- except BaseException as exc:
- if not future.done():
- future.set_exception(exc)
-
- future: Future[BlockingPortal] = Future()
- thread = Thread(target=run_blocking_portal, daemon=True, name=name)
- thread.start()
- try:
- cancel_remaining_tasks = False
- portal = future.result()
- try:
- yield portal
- except BaseException:
- cancel_remaining_tasks = True
- raise
- finally:
- try:
- portal.call(portal.stop, cancel_remaining_tasks)
- except RuntimeError:
- pass
- finally:
- thread.join()
-
-
-def check_cancelled() -> None:
- """
- Check if the cancel scope of the host task's running the current worker thread has
- been cancelled.
-
- If the host task's current cancel scope has indeed been cancelled, the
- backend-specific cancellation exception will be raised.
-
- :raises RuntimeError: if the current thread was not spawned by
- :func:`.to_thread.run_sync`
-
- """
- try:
- token: EventLoopToken = threadlocals.current_token
- except AttributeError:
- raise NoEventLoopError(
- "This function can only be called inside an AnyIO worker thread"
- ) from None
-
- token.backend_class.check_cancelled()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/functools.py b/backend/.venv/lib/python3.12/site-packages/anyio/functools.py
deleted file mode 100644
index b80afe6..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/functools.py
+++ /dev/null
@@ -1,375 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "AsyncCacheInfo",
- "AsyncCacheParameters",
- "AsyncLRUCacheWrapper",
- "cache",
- "lru_cache",
- "reduce",
-)
-
-import functools
-import sys
-from collections import OrderedDict
-from collections.abc import (
- AsyncIterable,
- Awaitable,
- Callable,
- Coroutine,
- Hashable,
- Iterable,
-)
-from functools import update_wrapper
-from inspect import iscoroutinefunction
-from typing import (
- Any,
- Generic,
- NamedTuple,
- TypedDict,
- TypeVar,
- cast,
- final,
- overload,
-)
-from weakref import WeakKeyDictionary
-
-from ._core._synchronization import Lock
-from .lowlevel import RunVar, checkpoint
-
-if sys.version_info >= (3, 11):
- from typing import ParamSpec
-else:
- from typing_extensions import ParamSpec
-
-T = TypeVar("T")
-S = TypeVar("S")
-P = ParamSpec("P")
-lru_cache_items: RunVar[
- WeakKeyDictionary[
- AsyncLRUCacheWrapper[Any, Any],
- OrderedDict[Hashable, tuple[_InitialMissingType, Lock] | tuple[Any, None]],
- ]
-] = RunVar("lru_cache_items")
-
-
-class _InitialMissingType:
- pass
-
-
-initial_missing: _InitialMissingType = _InitialMissingType()
-
-
-class AsyncCacheInfo(NamedTuple):
- hits: int
- misses: int
- maxsize: int | None
- currsize: int
-
-
-class AsyncCacheParameters(TypedDict):
- maxsize: int | None
- typed: bool
- always_checkpoint: bool
-
-
-class _LRUMethodWrapper(Generic[T]):
- def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object):
- self.__wrapper = wrapper
- self.__instance = instance
-
- def cache_info(self) -> AsyncCacheInfo:
- return self.__wrapper.cache_info()
-
- def cache_parameters(self) -> AsyncCacheParameters:
- return self.__wrapper.cache_parameters()
-
- def cache_clear(self) -> None:
- self.__wrapper.cache_clear()
-
- async def __call__(self, *args: Any, **kwargs: Any) -> T:
- if self.__instance is None:
- return await self.__wrapper(*args, **kwargs)
-
- return await self.__wrapper(self.__instance, *args, **kwargs)
-
-
-@final
-class AsyncLRUCacheWrapper(Generic[P, T]):
- def __init__(
- self,
- func: Callable[P, Awaitable[T]],
- maxsize: int | None,
- typed: bool,
- always_checkpoint: bool,
- ):
- self.__wrapped__ = func
- self._hits: int = 0
- self._misses: int = 0
- self._maxsize = max(maxsize, 0) if maxsize is not None else None
- self._currsize: int = 0
- self._typed = typed
- self._always_checkpoint = always_checkpoint
- update_wrapper(self, func)
-
- def cache_info(self) -> AsyncCacheInfo:
- return AsyncCacheInfo(self._hits, self._misses, self._maxsize, self._currsize)
-
- def cache_parameters(self) -> AsyncCacheParameters:
- return {
- "maxsize": self._maxsize,
- "typed": self._typed,
- "always_checkpoint": self._always_checkpoint,
- }
-
- def cache_clear(self) -> None:
- if cache := lru_cache_items.get(None):
- cache.pop(self, None)
- self._hits = self._misses = self._currsize = 0
-
- async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
- # Easy case first: if maxsize == 0, no caching is done
- if self._maxsize == 0:
- value = await self.__wrapped__(*args, **kwargs)
- self._misses += 1
- return value
-
- # The key is constructed as a flat tuple to avoid memory overhead
- key: tuple[Any, ...] = args
- if kwargs:
- # initial_missing is used as a separator
- key += (initial_missing,) + sum(kwargs.items(), ())
-
- if self._typed:
- key += tuple(type(arg) for arg in args)
- if kwargs:
- key += (initial_missing,) + tuple(type(val) for val in kwargs.values())
-
- try:
- cache = lru_cache_items.get()
- except LookupError:
- cache = WeakKeyDictionary()
- lru_cache_items.set(cache)
-
- try:
- cache_entry = cache[self]
- except KeyError:
- cache_entry = cache[self] = OrderedDict()
-
- cached_value: T | _InitialMissingType
- try:
- cached_value, lock = cache_entry[key]
- except KeyError:
- # We're the first task to call this function
- cached_value, lock = (
- initial_missing,
- Lock(fast_acquire=not self._always_checkpoint),
- )
- cache_entry[key] = cached_value, lock
-
- if lock is None:
- # The value was already cached
- self._hits += 1
- cache_entry.move_to_end(key)
- if self._always_checkpoint:
- await checkpoint()
-
- return cast(T, cached_value)
-
- async with lock:
- # Check if another task filled the cache while we acquired the lock
- if (cached_value := cache_entry[key][0]) is initial_missing:
- self._misses += 1
- if self._maxsize is not None and self._currsize >= self._maxsize:
- cache_entry.popitem(last=False)
- else:
- self._currsize += 1
-
- value = await self.__wrapped__(*args, **kwargs)
- cache_entry[key] = value, None
- else:
- # Another task filled the cache while we were waiting for the lock
- self._hits += 1
- cache_entry.move_to_end(key)
- value = cast(T, cached_value)
-
- return value
-
- def __get__(
- self, instance: object, owner: type | None = None
- ) -> _LRUMethodWrapper[T]:
- wrapper = _LRUMethodWrapper(self, instance)
- update_wrapper(wrapper, self.__wrapped__)
- return wrapper
-
-
-class _LRUCacheWrapper(Generic[T]):
- def __init__(self, maxsize: int | None, typed: bool, always_checkpoint: bool):
- self._maxsize = maxsize
- self._typed = typed
- self._always_checkpoint = always_checkpoint
-
- @overload
- def __call__( # type: ignore[overload-overlap]
- self, func: Callable[P, Coroutine[Any, Any, T]], /
- ) -> AsyncLRUCacheWrapper[P, T]: ...
-
- @overload
- def __call__(
- self, func: Callable[..., T], /
- ) -> functools._lru_cache_wrapper[T]: ...
-
- def __call__(
- self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], /
- ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]:
- if iscoroutinefunction(f):
- return AsyncLRUCacheWrapper(
- f, self._maxsize, self._typed, self._always_checkpoint
- )
-
- return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f) # type: ignore[arg-type]
-
-
-@overload
-def cache( # type: ignore[overload-overlap]
- func: Callable[P, Coroutine[Any, Any, T]], /
-) -> AsyncLRUCacheWrapper[P, T]: ...
-
-
-@overload
-def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ...
-
-
-def cache(
- func: Callable[..., T] | Callable[P, Coroutine[Any, Any, T]], /
-) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]:
- """
- A convenient shortcut for :func:`lru_cache` with ``maxsize=None``.
-
- This is the asynchronous equivalent to :func:`functools.cache`.
-
- """
- return lru_cache(maxsize=None)(func)
-
-
-@overload
-def lru_cache(
- *, maxsize: int | None = ..., typed: bool = ..., always_checkpoint: bool = ...
-) -> _LRUCacheWrapper[Any]: ...
-
-
-@overload
-def lru_cache( # type: ignore[overload-overlap]
- func: Callable[P, Coroutine[Any, Any, T]], /
-) -> AsyncLRUCacheWrapper[P, T]: ...
-
-
-@overload
-def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ...
-
-
-def lru_cache(
- func: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T] | None = None,
- /,
- *,
- maxsize: int | None = 128,
- typed: bool = False,
- always_checkpoint: bool = False,
-) -> (
- AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T] | _LRUCacheWrapper[Any]
-):
- """
- An asynchronous version of :func:`functools.lru_cache`.
-
- If a synchronous function is passed, the standard library
- :func:`functools.lru_cache` is applied instead.
-
- :param always_checkpoint: if ``True``, every call to the cached function will be
- guaranteed to yield control to the event loop at least once
-
- .. note:: Caches and locks are managed on a per-event loop basis.
-
- """
- if func is None:
- return _LRUCacheWrapper[Any](maxsize, typed, always_checkpoint)
-
- if not callable(func):
- raise TypeError("the first argument must be callable")
-
- return _LRUCacheWrapper[T](maxsize, typed, always_checkpoint)(func)
-
-
-@overload
-async def reduce(
- function: Callable[[T, S], Awaitable[T]],
- iterable: Iterable[S] | AsyncIterable[S],
- /,
- initial: T,
-) -> T: ...
-
-
-@overload
-async def reduce(
- function: Callable[[T, T], Awaitable[T]],
- iterable: Iterable[T] | AsyncIterable[T],
- /,
-) -> T: ...
-
-
-async def reduce( # type: ignore[misc]
- function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]],
- iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S],
- /,
- initial: T | _InitialMissingType = initial_missing,
-) -> T:
- """
- Asynchronous version of :func:`functools.reduce`.
-
- :param function: a coroutine function that takes two arguments: the accumulated
- value and the next element from the iterable
- :param iterable: an iterable or async iterable
- :param initial: the initial value (if missing, the first element of the iterable is
- used as the initial value)
-
- """
- element: Any
- function_called = False
- if isinstance(iterable, AsyncIterable):
- async_it = iterable.__aiter__()
- if initial is initial_missing:
- try:
- value = cast(T, await async_it.__anext__())
- except StopAsyncIteration:
- raise TypeError(
- "reduce() of empty sequence with no initial value"
- ) from None
- else:
- value = cast(T, initial)
-
- async for element in async_it:
- value = await function(value, element)
- function_called = True
- elif isinstance(iterable, Iterable):
- it = iter(iterable)
- if initial is initial_missing:
- try:
- value = cast(T, next(it))
- except StopIteration:
- raise TypeError(
- "reduce() of empty sequence with no initial value"
- ) from None
- else:
- value = cast(T, initial)
-
- for element in it:
- value = await function(value, element)
- function_called = True
- else:
- raise TypeError("reduce() argument 2 must be an iterable or async iterable")
-
- # Make sure there is at least one checkpoint, even if an empty iterable and an
- # initial value were given
- if not function_called:
- await checkpoint()
-
- return value
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/lowlevel.py b/backend/.venv/lib/python3.12/site-packages/anyio/lowlevel.py
deleted file mode 100644
index ffbb75a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/lowlevel.py
+++ /dev/null
@@ -1,196 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "EventLoopToken",
- "RunvarToken",
- "RunVar",
- "checkpoint",
- "checkpoint_if_cancelled",
- "cancel_shielded_checkpoint",
- "current_token",
-)
-
-import enum
-from dataclasses import dataclass
-from types import TracebackType
-from typing import Any, Generic, Literal, TypeVar, final, overload
-from weakref import WeakKeyDictionary
-
-from ._core._eventloop import get_async_backend
-from .abc import AsyncBackend
-
-T = TypeVar("T")
-D = TypeVar("D")
-
-
-async def checkpoint() -> None:
- """
- Check for cancellation and allow the scheduler to switch to another task.
-
- Equivalent to (but more efficient than)::
-
- await checkpoint_if_cancelled()
- await cancel_shielded_checkpoint()
-
- .. versionadded:: 3.0
-
- """
- await get_async_backend().checkpoint()
-
-
-async def checkpoint_if_cancelled() -> None:
- """
- Enter a checkpoint if the enclosing cancel scope has been cancelled.
-
- This does not allow the scheduler to switch to a different task.
-
- .. versionadded:: 3.0
-
- """
- await get_async_backend().checkpoint_if_cancelled()
-
-
-async def cancel_shielded_checkpoint() -> None:
- """
- Allow the scheduler to switch to another task but without checking for cancellation.
-
- Equivalent to (but potentially more efficient than)::
-
- with CancelScope(shield=True):
- await checkpoint()
-
- .. versionadded:: 3.0
-
- """
- await get_async_backend().cancel_shielded_checkpoint()
-
-
-@final
-@dataclass(frozen=True, repr=False)
-class EventLoopToken:
- """
- An opaque object that holds a reference to an event loop.
-
- .. versionadded:: 4.11.0
- """
-
- backend_class: type[AsyncBackend]
- native_token: object
-
-
-def current_token() -> EventLoopToken:
- """
- Return a token object that can be used to call code in the current event loop from
- another thread.
-
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- .. versionadded:: 4.11.0
-
- """
- backend_class = get_async_backend()
- raw_token = backend_class.current_token()
- return EventLoopToken(backend_class, raw_token)
-
-
-_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary()
-
-
-class _NoValueSet(enum.Enum):
- NO_VALUE_SET = enum.auto()
-
-
-class RunvarToken(Generic[T]):
- __slots__ = "_var", "_value", "_redeemed"
-
- def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]):
- self._var = var
- self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value
- self._redeemed = False
-
- def __enter__(self) -> RunvarToken[T]:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self._var.reset(self)
-
-
-class RunVar(Generic[T]):
- """
- Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop.
-
- Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that
- will reset the variable to its previous value when the context block is exited.
- """
-
- __slots__ = "_name", "_default"
-
- NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET
-
- def __init__(
- self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
- ):
- self._name = name
- self._default = default
-
- @property
- def _current_vars(self) -> dict[RunVar[T], T]:
- native_token = current_token().native_token
- try:
- return _run_vars[native_token]
- except KeyError:
- run_vars = _run_vars[native_token] = {}
- return run_vars
-
- @overload
- def get(self, default: D) -> T | D: ...
-
- @overload
- def get(self) -> T: ...
-
- def get(
- self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
- ) -> T | D:
- try:
- return self._current_vars[self]
- except KeyError:
- if default is not RunVar.NO_VALUE_SET:
- return default
- elif self._default is not RunVar.NO_VALUE_SET:
- return self._default
-
- raise LookupError(
- f'Run variable "{self._name}" has no value and no default set'
- )
-
- def set(self, value: T) -> RunvarToken[T]:
- current_vars = self._current_vars
- token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET))
- current_vars[self] = value
- return token
-
- def reset(self, token: RunvarToken[T]) -> None:
- if token._var is not self:
- raise ValueError("This token does not belong to this RunVar")
-
- if token._redeemed:
- raise ValueError("This token has already been used")
-
- if token._value is _NoValueSet.NO_VALUE_SET:
- try:
- del self._current_vars[self]
- except KeyError:
- pass
- else:
- self._current_vars[self] = token._value
-
- token._redeemed = True
-
- def __repr__(self) -> str:
- return f""
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/py.typed b/backend/.venv/lib/python3.12/site-packages/anyio/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/pytest_plugin.py b/backend/.venv/lib/python3.12/site-packages/anyio/pytest_plugin.py
deleted file mode 100644
index 4222816..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/pytest_plugin.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from __future__ import annotations
-
-import socket
-import sys
-from collections.abc import Callable, Generator, Iterator
-from contextlib import ExitStack, contextmanager
-from inspect import isasyncgenfunction, iscoroutinefunction, ismethod
-from typing import Any, cast
-
-import pytest
-from _pytest.fixtures import SubRequest
-from _pytest.outcomes import Exit
-
-from . import get_available_backends
-from ._core._eventloop import (
- current_async_library,
- get_async_backend,
- reset_current_async_library,
- set_current_async_library,
-)
-from ._core._exceptions import iterate_exceptions
-from .abc import TestRunner
-
-if sys.version_info < (3, 11):
- from exceptiongroup import ExceptionGroup
-
-_current_runner: TestRunner | None = None
-_runner_stack: ExitStack | None = None
-_runner_leases = 0
-
-
-def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]:
- if isinstance(backend, str):
- return backend, {}
- elif isinstance(backend, tuple) and len(backend) == 2:
- if isinstance(backend[0], str) and isinstance(backend[1], dict):
- return cast(tuple[str, dict[str, Any]], backend)
-
- raise TypeError("anyio_backend must be either a string or tuple of (string, dict)")
-
-
-@contextmanager
-def get_runner(
- backend_name: str, backend_options: dict[str, Any]
-) -> Iterator[TestRunner]:
- global _current_runner, _runner_leases, _runner_stack
- if _current_runner is None:
- asynclib = get_async_backend(backend_name)
- _runner_stack = ExitStack()
- if current_async_library() is None:
- # Since we're in control of the event loop, we can cache the name of the
- # async library
- token = set_current_async_library(backend_name)
- _runner_stack.callback(reset_current_async_library, token)
-
- backend_options = backend_options or {}
- _current_runner = _runner_stack.enter_context(
- asynclib.create_test_runner(backend_options)
- )
-
- _runner_leases += 1
- try:
- yield _current_runner
- finally:
- _runner_leases -= 1
- if not _runner_leases:
- assert _runner_stack is not None
- _runner_stack.close()
- _runner_stack = _current_runner = None
-
-
-def pytest_addoption(parser: pytest.Parser) -> None:
- parser.addini(
- "anyio_mode",
- default="strict",
- help='AnyIO plugin mode (either "strict" or "auto")',
- )
-
-
-def pytest_configure(config: pytest.Config) -> None:
- config.addinivalue_line(
- "markers",
- "anyio: mark the (coroutine function) test to be run asynchronously via anyio.",
- )
- if (
- config.getini("anyio_mode") == "auto"
- and config.pluginmanager.has_plugin("asyncio")
- and config.getini("asyncio_mode") == "auto"
- ):
- config.issue_config_time_warning(
- pytest.PytestConfigWarning(
- "AnyIO auto mode has been enabled together with pytest-asyncio auto "
- "mode. This may cause unexpected behavior."
- ),
- 1,
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]:
- def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any:
- # Rebind any fixture methods to the request instance
- if (
- request.instance
- and ismethod(func)
- and type(func.__self__) is type(request.instance)
- ):
- local_func = func.__func__.__get__(request.instance)
- else:
- local_func = func
-
- backend_name, backend_options = extract_backend_and_options(anyio_backend)
- if has_backend_arg:
- kwargs["anyio_backend"] = anyio_backend
-
- if has_request_arg:
- kwargs["request"] = request
-
- with get_runner(backend_name, backend_options) as runner:
- if isasyncgenfunction(local_func):
- yield from runner.run_asyncgen_fixture(local_func, kwargs)
- else:
- yield runner.run_fixture(local_func, kwargs)
-
- # Only apply this to coroutine functions and async generator functions in requests
- # that involve the anyio_backend fixture
- func = fixturedef.func
- if isasyncgenfunction(func) or iscoroutinefunction(func):
- if "anyio_backend" in request.fixturenames:
- fixturedef.func = wrapper
- original_argname = fixturedef.argnames
-
- if not (has_backend_arg := "anyio_backend" in fixturedef.argnames):
- fixturedef.argnames += ("anyio_backend",)
-
- if not (has_request_arg := "request" in fixturedef.argnames):
- fixturedef.argnames += ("request",)
-
- try:
- return (yield)
- finally:
- fixturedef.func = func
- fixturedef.argnames = original_argname
-
- return (yield)
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_pycollect_makeitem(
- collector: pytest.Module | pytest.Class, name: str, obj: object
-) -> None:
- if collector.istestfunction(obj, name):
- inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj
- if iscoroutinefunction(inner_func):
- anyio_auto_mode = collector.config.getini("anyio_mode") == "auto"
- marker = collector.get_closest_marker("anyio")
- own_markers = getattr(obj, "pytestmark", ())
- if (
- anyio_auto_mode
- or marker
- or any(marker.name == "anyio" for marker in own_markers)
- ):
- pytest.mark.usefixtures("anyio_backend")(obj)
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None:
- def run_with_hypothesis(**kwargs: Any) -> None:
- with get_runner(backend_name, backend_options) as runner:
- runner.run_test(original_func, kwargs)
-
- backend = pyfuncitem.funcargs.get("anyio_backend")
- if backend:
- backend_name, backend_options = extract_backend_and_options(backend)
-
- if hasattr(pyfuncitem.obj, "hypothesis"):
- # Wrap the inner test function unless it's already wrapped
- original_func = pyfuncitem.obj.hypothesis.inner_test
- if original_func.__qualname__ != run_with_hypothesis.__qualname__:
- if iscoroutinefunction(original_func):
- pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis
-
- return None
-
- if iscoroutinefunction(pyfuncitem.obj):
- funcargs = pyfuncitem.funcargs
- testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
- with get_runner(backend_name, backend_options) as runner:
- try:
- runner.run_test(pyfuncitem.obj, testargs)
- except ExceptionGroup as excgrp:
- for exc in iterate_exceptions(excgrp):
- if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)):
- raise exc from excgrp
-
- raise
-
- return True
-
- return None
-
-
-@pytest.fixture(scope="module", params=get_available_backends())
-def anyio_backend(request: Any) -> Any:
- return request.param
-
-
-@pytest.fixture
-def anyio_backend_name(anyio_backend: Any) -> str:
- if isinstance(anyio_backend, str):
- return anyio_backend
- else:
- return anyio_backend[0]
-
-
-@pytest.fixture
-def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]:
- if isinstance(anyio_backend, str):
- return {}
- else:
- return anyio_backend[1]
-
-
-class FreePortFactory:
- """
- Manages port generation based on specified socket kind, ensuring no duplicate
- ports are generated.
-
- This class provides functionality for generating available free ports on the
- system. It is initialized with a specific socket kind and can generate ports
- for given address families while avoiding reuse of previously generated ports.
-
- Users should not instantiate this class directly, but use the
- ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple
- uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead.
- """
-
- def __init__(self, kind: socket.SocketKind) -> None:
- self._kind = kind
- self._generated = set[int]()
-
- @property
- def kind(self) -> socket.SocketKind:
- """
- The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or
- :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability
-
- """
- return self._kind
-
- def __call__(self, family: socket.AddressFamily | None = None) -> int:
- """
- Return an unbound port for the given address family.
-
- :param family: if omitted, both IPv4 and IPv6 addresses will be tried
- :return: a port number
-
- """
- if family is not None:
- families = [family]
- else:
- families = [socket.AF_INET]
- if socket.has_ipv6:
- families.append(socket.AF_INET6)
-
- while True:
- port = 0
- with ExitStack() as stack:
- for family in families:
- sock = stack.enter_context(socket.socket(family, self._kind))
- addr = "::1" if family == socket.AF_INET6 else "127.0.0.1"
- try:
- sock.bind((addr, port))
- except OSError:
- break
-
- if not port:
- port = sock.getsockname()[1]
- else:
- if port not in self._generated:
- self._generated.add(port)
- return port
-
-
-@pytest.fixture(scope="session")
-def free_tcp_port_factory() -> FreePortFactory:
- return FreePortFactory(socket.SOCK_STREAM)
-
-
-@pytest.fixture(scope="session")
-def free_udp_port_factory() -> FreePortFactory:
- return FreePortFactory(socket.SOCK_DGRAM)
-
-
-@pytest.fixture
-def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int:
- return free_tcp_port_factory()
-
-
-@pytest.fixture
-def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int:
- return free_udp_port_factory()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__init__.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 7bb5659..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc
deleted file mode 100644
index 57a5591..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc
deleted file mode 100644
index 391629d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc
deleted file mode 100644
index 8331721..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc
deleted file mode 100644
index a489800..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc
deleted file mode 100644
index 1a13c57..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc
deleted file mode 100644
index 4fa5231..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/buffered.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/buffered.py
deleted file mode 100644
index 57c7cd7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/buffered.py
+++ /dev/null
@@ -1,188 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "BufferedByteReceiveStream",
- "BufferedByteStream",
- "BufferedConnectable",
-)
-
-import sys
-from collections.abc import Callable, Iterable, Mapping
-from dataclasses import dataclass, field
-from typing import Any, SupportsIndex
-
-from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead
-from ..abc import (
- AnyByteReceiveStream,
- AnyByteStream,
- AnyByteStreamConnectable,
- ByteReceiveStream,
- ByteStream,
- ByteStreamConnectable,
-)
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
-
-@dataclass(eq=False)
-class BufferedByteReceiveStream(ByteReceiveStream):
- """
- Wraps any bytes-based receive stream and uses a buffer to provide sophisticated
- receiving capabilities in the form of a byte stream.
- """
-
- receive_stream: AnyByteReceiveStream
- _buffer: bytearray = field(init=False, default_factory=bytearray)
- _closed: bool = field(init=False, default=False)
-
- async def aclose(self) -> None:
- await self.receive_stream.aclose()
- self._closed = True
-
- @property
- def buffer(self) -> bytes:
- """The bytes currently in the buffer."""
- return bytes(self._buffer)
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return self.receive_stream.extra_attributes
-
- def feed_data(self, data: Iterable[SupportsIndex], /) -> None:
- """
- Append data directly into the buffer.
-
- Any data in the buffer will be consumed by receive operations before receiving
- anything from the wrapped stream.
-
- :param data: the data to append to the buffer (can be bytes or anything else
- that supports ``__index__()``)
-
- """
- self._buffer.extend(data)
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- if self._closed:
- raise ClosedResourceError
-
- if self._buffer:
- chunk = bytes(self._buffer[:max_bytes])
- del self._buffer[:max_bytes]
- return chunk
- elif isinstance(self.receive_stream, ByteReceiveStream):
- return await self.receive_stream.receive(max_bytes)
- else:
- # With a bytes-oriented object stream, we need to handle any surplus bytes
- # we get from the receive() call
- chunk = await self.receive_stream.receive()
- if len(chunk) > max_bytes:
- # Save the surplus bytes in the buffer
- self._buffer.extend(chunk[max_bytes:])
- return chunk[:max_bytes]
- else:
- return chunk
-
- async def receive_exactly(self, nbytes: int) -> bytes:
- """
- Read exactly the given amount of bytes from the stream.
-
- :param nbytes: the number of bytes to read
- :return: the bytes read
- :raises ~anyio.IncompleteRead: if the stream was closed before the requested
- amount of bytes could be read from the stream
-
- """
- while True:
- remaining = nbytes - len(self._buffer)
- if remaining <= 0:
- retval = self._buffer[:nbytes]
- del self._buffer[:nbytes]
- return bytes(retval)
-
- try:
- if isinstance(self.receive_stream, ByteReceiveStream):
- chunk = await self.receive_stream.receive(remaining)
- else:
- chunk = await self.receive_stream.receive()
- except EndOfStream as exc:
- raise IncompleteRead from exc
-
- self._buffer.extend(chunk)
-
- async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes:
- """
- Read from the stream until the delimiter is found or max_bytes have been read.
-
- :param delimiter: the marker to look for in the stream
- :param max_bytes: maximum number of bytes that will be read before raising
- :exc:`~anyio.DelimiterNotFound`
- :return: the bytes read (not including the delimiter)
- :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter
- was found
- :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the
- bytes read up to the maximum allowed
-
- """
- delimiter_size = len(delimiter)
- offset = 0
- while True:
- # Check if the delimiter can be found in the current buffer
- index = self._buffer.find(delimiter, offset)
- if index >= 0:
- found = self._buffer[:index]
- del self._buffer[: index + len(delimiter) :]
- return bytes(found)
-
- # Check if the buffer is already at or over the limit
- if len(self._buffer) >= max_bytes:
- raise DelimiterNotFound(max_bytes)
-
- # Read more data into the buffer from the socket
- try:
- data = await self.receive_stream.receive()
- except EndOfStream as exc:
- raise IncompleteRead from exc
-
- # Move the offset forward and add the new data to the buffer
- offset = max(len(self._buffer) - delimiter_size + 1, 0)
- self._buffer.extend(data)
-
-
-class BufferedByteStream(BufferedByteReceiveStream, ByteStream):
- """
- A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed
- through to the wrapped stream as-is.
- """
-
- def __init__(self, stream: AnyByteStream):
- """
- :param stream: the stream to be wrapped
-
- """
- super().__init__(stream)
- self._stream = stream
-
- @override
- async def send_eof(self) -> None:
- await self._stream.send_eof()
-
- @override
- async def send(self, item: bytes) -> None:
- await self._stream.send(item)
-
-
-class BufferedConnectable(ByteStreamConnectable):
- def __init__(self, connectable: AnyByteStreamConnectable):
- """
- :param connectable: the connectable to wrap
-
- """
- self.connectable = connectable
-
- @override
- async def connect(self) -> BufferedByteStream:
- stream = await self.connectable.connect()
- return BufferedByteStream(stream)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/file.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/file.py
deleted file mode 100644
index 82d2da8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/file.py
+++ /dev/null
@@ -1,154 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "FileReadStream",
- "FileStreamAttribute",
- "FileWriteStream",
-)
-
-from collections.abc import Callable, Mapping
-from io import SEEK_SET, UnsupportedOperation
-from os import PathLike
-from pathlib import Path
-from typing import Any, BinaryIO, cast
-
-from .. import (
- BrokenResourceError,
- ClosedResourceError,
- EndOfStream,
- TypedAttributeSet,
- to_thread,
- typed_attribute,
-)
-from ..abc import ByteReceiveStream, ByteSendStream
-
-
-class FileStreamAttribute(TypedAttributeSet):
- #: the open file descriptor
- file: BinaryIO = typed_attribute()
- #: the path of the file on the file system, if available (file must be a real file)
- path: Path = typed_attribute()
- #: the file number, if available (file must be a real file or a TTY)
- fileno: int = typed_attribute()
-
-
-class _BaseFileStream:
- def __init__(self, file: BinaryIO):
- self._file = file
-
- async def aclose(self) -> None:
- await to_thread.run_sync(self._file.close)
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- attributes: dict[Any, Callable[[], Any]] = {
- FileStreamAttribute.file: lambda: self._file,
- }
-
- if hasattr(self._file, "name"):
- attributes[FileStreamAttribute.path] = lambda: Path(self._file.name)
-
- try:
- self._file.fileno()
- except UnsupportedOperation:
- pass
- else:
- attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno()
-
- return attributes
-
-
-class FileReadStream(_BaseFileStream, ByteReceiveStream):
- """
- A byte stream that reads from a file in the file system.
-
- :param file: a file that has been opened for reading in binary mode
-
- .. versionadded:: 3.0
- """
-
- @classmethod
- async def from_path(cls, path: str | PathLike[str]) -> FileReadStream:
- """
- Create a file read stream by opening the given file.
-
- :param path: path of the file to read from
-
- """
- file = await to_thread.run_sync(Path(path).open, "rb")
- return cls(cast(BinaryIO, file))
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- try:
- data = await to_thread.run_sync(self._file.read, max_bytes)
- except ValueError:
- raise ClosedResourceError from None
- except OSError as exc:
- raise BrokenResourceError from exc
-
- if data:
- return data
- else:
- raise EndOfStream
-
- async def seek(self, position: int, whence: int = SEEK_SET) -> int:
- """
- Seek the file to the given position.
-
- .. seealso:: :meth:`io.IOBase.seek`
-
- .. note:: Not all file descriptors are seekable.
-
- :param position: position to seek the file to
- :param whence: controls how ``position`` is interpreted
- :return: the new absolute position
- :raises OSError: if the file is not seekable
-
- """
- return await to_thread.run_sync(self._file.seek, position, whence)
-
- async def tell(self) -> int:
- """
- Return the current stream position.
-
- .. note:: Not all file descriptors are seekable.
-
- :return: the current absolute position
- :raises OSError: if the file is not seekable
-
- """
- return await to_thread.run_sync(self._file.tell)
-
-
-class FileWriteStream(_BaseFileStream, ByteSendStream):
- """
- A byte stream that writes to a file in the file system.
-
- :param file: a file that has been opened for writing in binary mode
-
- .. versionadded:: 3.0
- """
-
- @classmethod
- async def from_path(
- cls, path: str | PathLike[str], append: bool = False
- ) -> FileWriteStream:
- """
- Create a file write stream by opening the given file for writing.
-
- :param path: path of the file to write to
- :param append: if ``True``, open the file for appending; if ``False``, any
- existing file at the given path will be truncated
-
- """
- mode = "ab" if append else "wb"
- file = await to_thread.run_sync(Path(path).open, mode)
- return cls(cast(BinaryIO, file))
-
- async def send(self, item: bytes) -> None:
- try:
- await to_thread.run_sync(self._file.write, item)
- except ValueError:
- raise ClosedResourceError from None
- except OSError as exc:
- raise BrokenResourceError from exc
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/memory.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/memory.py
deleted file mode 100644
index a3fa0c3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/memory.py
+++ /dev/null
@@ -1,325 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "MemoryObjectReceiveStream",
- "MemoryObjectSendStream",
- "MemoryObjectStreamStatistics",
-)
-
-import warnings
-from collections import OrderedDict, deque
-from dataclasses import dataclass, field
-from types import TracebackType
-from typing import Generic, NamedTuple, TypeVar
-
-from .. import (
- BrokenResourceError,
- ClosedResourceError,
- EndOfStream,
- WouldBlock,
-)
-from .._core._testing import TaskInfo, get_current_task
-from ..abc import Event, ObjectReceiveStream, ObjectSendStream
-from ..lowlevel import checkpoint
-
-T_Item = TypeVar("T_Item")
-T_co = TypeVar("T_co", covariant=True)
-T_contra = TypeVar("T_contra", contravariant=True)
-
-
-class MemoryObjectStreamStatistics(NamedTuple):
- current_buffer_used: int #: number of items stored in the buffer
- #: maximum number of items that can be stored on this stream (or :data:`math.inf`)
- max_buffer_size: float
- open_send_streams: int #: number of unclosed clones of the send stream
- open_receive_streams: int #: number of unclosed clones of the receive stream
- #: number of tasks blocked on :meth:`MemoryObjectSendStream.send`
- tasks_waiting_send: int
- #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive`
- tasks_waiting_receive: int
-
-
-@dataclass(eq=False)
-class _MemoryObjectItemReceiver(Generic[T_Item]):
- task_info: TaskInfo = field(init=False, default_factory=get_current_task)
- item: T_Item = field(init=False)
-
- def __repr__(self) -> str:
- # When item is not defined, we get following error with default __repr__:
- # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item'
- item = getattr(self, "item", None)
- return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})"
-
-
-@dataclass(eq=False)
-class _MemoryObjectStreamState(Generic[T_Item]):
- max_buffer_size: float = field()
- buffer: deque[T_Item] = field(init=False, default_factory=deque)
- open_send_channels: int = field(init=False, default=0)
- open_receive_channels: int = field(init=False, default=0)
- waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field(
- init=False, default_factory=OrderedDict
- )
- waiting_senders: OrderedDict[Event, T_Item] = field(
- init=False, default_factory=OrderedDict
- )
-
- def statistics(self) -> MemoryObjectStreamStatistics:
- return MemoryObjectStreamStatistics(
- len(self.buffer),
- self.max_buffer_size,
- self.open_send_channels,
- self.open_receive_channels,
- len(self.waiting_senders),
- len(self.waiting_receivers),
- )
-
-
-@dataclass(eq=False)
-class MemoryObjectReceiveStream(Generic[T_co], ObjectReceiveStream[T_co]):
- _state: _MemoryObjectStreamState[T_co]
- _closed: bool = field(init=False, default=False)
-
- def __post_init__(self) -> None:
- self._state.open_receive_channels += 1
-
- def receive_nowait(self) -> T_co:
- """
- Receive the next item if it can be done without waiting.
-
- :return: the received item
- :raises ~anyio.ClosedResourceError: if this send stream has been closed
- :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been
- closed from the sending end
- :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks
- waiting to send
-
- """
- if self._closed:
- raise ClosedResourceError
-
- if self._state.waiting_senders:
- # Get the item from the next sender
- send_event, item = self._state.waiting_senders.popitem(last=False)
- self._state.buffer.append(item)
- send_event.set()
-
- if self._state.buffer:
- return self._state.buffer.popleft()
- elif not self._state.open_send_channels:
- raise EndOfStream
-
- raise WouldBlock
-
- async def receive(self) -> T_co:
- await checkpoint()
- try:
- return self.receive_nowait()
- except WouldBlock:
- # Add ourselves in the queue
- receive_event = Event()
- receiver = _MemoryObjectItemReceiver[T_co]()
- self._state.waiting_receivers[receive_event] = receiver
-
- try:
- await receive_event.wait()
- finally:
- self._state.waiting_receivers.pop(receive_event, None)
-
- try:
- return receiver.item
- except AttributeError:
- raise EndOfStream from None
-
- def clone(self) -> MemoryObjectReceiveStream[T_co]:
- """
- Create a clone of this receive stream.
-
- Each clone can be closed separately. Only when all clones have been closed will
- the receiving end of the memory stream be considered closed by the sending ends.
-
- :return: the cloned stream
-
- """
- if self._closed:
- raise ClosedResourceError
-
- return MemoryObjectReceiveStream(_state=self._state)
-
- def close(self) -> None:
- """
- Close the stream.
-
- This works the exact same way as :meth:`aclose`, but is provided as a special
- case for the benefit of synchronous callbacks.
-
- """
- if not self._closed:
- self._closed = True
- self._state.open_receive_channels -= 1
- if self._state.open_receive_channels == 0:
- send_events = list(self._state.waiting_senders.keys())
- for event in send_events:
- event.set()
-
- async def aclose(self) -> None:
- self.close()
-
- def statistics(self) -> MemoryObjectStreamStatistics:
- """
- Return statistics about the current state of this stream.
-
- .. versionadded:: 3.0
- """
- return self._state.statistics()
-
- def __enter__(self) -> MemoryObjectReceiveStream[T_co]:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.close()
-
- def __del__(self) -> None:
- if not self._closed:
- warnings.warn(
- f"Unclosed <{self.__class__.__name__} at {id(self):x}>",
- ResourceWarning,
- stacklevel=1,
- source=self,
- )
-
-
-@dataclass(eq=False)
-class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]):
- _state: _MemoryObjectStreamState[T_contra]
- _closed: bool = field(init=False, default=False)
-
- def __post_init__(self) -> None:
- self._state.open_send_channels += 1
-
- def send_nowait(self, item: T_contra) -> None:
- """
- Send an item immediately if it can be done without waiting.
-
- :param item: the item to send
- :raises ~anyio.ClosedResourceError: if this send stream has been closed
- :raises ~anyio.BrokenResourceError: if the stream has been closed from the
- receiving end
- :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting
- to receive
-
- """
- if self._closed:
- raise ClosedResourceError
- if not self._state.open_receive_channels:
- raise BrokenResourceError
-
- while self._state.waiting_receivers:
- receive_event, receiver = self._state.waiting_receivers.popitem(last=False)
- if not receiver.task_info.has_pending_cancellation():
- receiver.item = item
- receive_event.set()
- return
-
- if len(self._state.buffer) < self._state.max_buffer_size:
- self._state.buffer.append(item)
- else:
- raise WouldBlock
-
- async def send(self, item: T_contra) -> None:
- """
- Send an item to the stream.
-
- If the buffer is full, this method blocks until there is again room in the
- buffer or the item can be sent directly to a receiver.
-
- :param item: the item to send
- :raises ~anyio.ClosedResourceError: if this send stream has been closed
- :raises ~anyio.BrokenResourceError: if the stream has been closed from the
- receiving end
-
- """
- await checkpoint()
- try:
- self.send_nowait(item)
- except WouldBlock:
- # Wait until there's someone on the receiving end
- send_event = Event()
- self._state.waiting_senders[send_event] = item
- try:
- await send_event.wait()
- except BaseException:
- self._state.waiting_senders.pop(send_event, None)
- raise
-
- if send_event in self._state.waiting_senders:
- del self._state.waiting_senders[send_event]
- raise BrokenResourceError from None
-
- def clone(self) -> MemoryObjectSendStream[T_contra]:
- """
- Create a clone of this send stream.
-
- Each clone can be closed separately. Only when all clones have been closed will
- the sending end of the memory stream be considered closed by the receiving ends.
-
- :return: the cloned stream
-
- """
- if self._closed:
- raise ClosedResourceError
-
- return MemoryObjectSendStream(_state=self._state)
-
- def close(self) -> None:
- """
- Close the stream.
-
- This works the exact same way as :meth:`aclose`, but is provided as a special
- case for the benefit of synchronous callbacks.
-
- """
- if not self._closed:
- self._closed = True
- self._state.open_send_channels -= 1
- if self._state.open_send_channels == 0:
- receive_events = list(self._state.waiting_receivers.keys())
- self._state.waiting_receivers.clear()
- for event in receive_events:
- event.set()
-
- async def aclose(self) -> None:
- self.close()
-
- def statistics(self) -> MemoryObjectStreamStatistics:
- """
- Return statistics about the current state of this stream.
-
- .. versionadded:: 3.0
- """
- return self._state.statistics()
-
- def __enter__(self) -> MemoryObjectSendStream[T_contra]:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.close()
-
- def __del__(self) -> None:
- if not self._closed:
- warnings.warn(
- f"Unclosed <{self.__class__.__name__} at {id(self):x}>",
- ResourceWarning,
- stacklevel=1,
- source=self,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/stapled.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/stapled.py
deleted file mode 100644
index 9248b68..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/stapled.py
+++ /dev/null
@@ -1,147 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "MultiListener",
- "StapledByteStream",
- "StapledObjectStream",
-)
-
-from collections.abc import Callable, Mapping, Sequence
-from dataclasses import dataclass
-from typing import Any, Generic, TypeVar
-
-from ..abc import (
- ByteReceiveStream,
- ByteSendStream,
- ByteStream,
- Listener,
- ObjectReceiveStream,
- ObjectSendStream,
- ObjectStream,
- TaskGroup,
-)
-
-T_Item = TypeVar("T_Item")
-T_Stream = TypeVar("T_Stream")
-
-
-@dataclass(eq=False)
-class StapledByteStream(ByteStream):
- """
- Combines two byte streams into a single, bidirectional byte stream.
-
- Extra attributes will be provided from both streams, with the receive stream
- providing the values in case of a conflict.
-
- :param ByteSendStream send_stream: the sending byte stream
- :param ByteReceiveStream receive_stream: the receiving byte stream
- """
-
- send_stream: ByteSendStream
- receive_stream: ByteReceiveStream
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- return await self.receive_stream.receive(max_bytes)
-
- async def send(self, item: bytes) -> None:
- await self.send_stream.send(item)
-
- async def send_eof(self) -> None:
- await self.send_stream.aclose()
-
- async def aclose(self) -> None:
- await self.send_stream.aclose()
- await self.receive_stream.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return {
- **self.send_stream.extra_attributes,
- **self.receive_stream.extra_attributes,
- }
-
-
-@dataclass(eq=False)
-class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]):
- """
- Combines two object streams into a single, bidirectional object stream.
-
- Extra attributes will be provided from both streams, with the receive stream
- providing the values in case of a conflict.
-
- :param ObjectSendStream send_stream: the sending object stream
- :param ObjectReceiveStream receive_stream: the receiving object stream
- """
-
- send_stream: ObjectSendStream[T_Item]
- receive_stream: ObjectReceiveStream[T_Item]
-
- async def receive(self) -> T_Item:
- return await self.receive_stream.receive()
-
- async def send(self, item: T_Item) -> None:
- await self.send_stream.send(item)
-
- async def send_eof(self) -> None:
- await self.send_stream.aclose()
-
- async def aclose(self) -> None:
- await self.send_stream.aclose()
- await self.receive_stream.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return {
- **self.send_stream.extra_attributes,
- **self.receive_stream.extra_attributes,
- }
-
-
-@dataclass(eq=False)
-class MultiListener(Generic[T_Stream], Listener[T_Stream]):
- """
- Combines multiple listeners into one, serving connections from all of them at once.
-
- Any MultiListeners in the given collection of listeners will have their listeners
- moved into this one.
-
- Extra attributes are provided from each listener, with each successive listener
- overriding any conflicting attributes from the previous one.
-
- :param listeners: listeners to serve
- :type listeners: Sequence[Listener[T_Stream]]
- """
-
- listeners: Sequence[Listener[T_Stream]]
-
- def __post_init__(self) -> None:
- listeners: list[Listener[T_Stream]] = []
- for listener in self.listeners:
- if isinstance(listener, MultiListener):
- listeners.extend(listener.listeners)
- del listener.listeners[:] # type: ignore[attr-defined]
- else:
- listeners.append(listener)
-
- self.listeners = listeners
-
- async def serve(
- self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None
- ) -> None:
- from .. import create_task_group
-
- async with create_task_group() as tg:
- for listener in self.listeners:
- tg.start_soon(listener.serve, handler, task_group)
-
- async def aclose(self) -> None:
- for listener in self.listeners:
- await listener.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- attributes: dict = {}
- for listener in self.listeners:
- attributes.update(listener.extra_attributes)
-
- return attributes
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/text.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/text.py
deleted file mode 100644
index 296cd25..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/text.py
+++ /dev/null
@@ -1,176 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "TextConnectable",
- "TextReceiveStream",
- "TextSendStream",
- "TextStream",
-)
-
-import codecs
-import sys
-from collections.abc import Callable, Mapping
-from dataclasses import InitVar, dataclass, field
-from typing import Any
-
-from ..abc import (
- AnyByteReceiveStream,
- AnyByteSendStream,
- AnyByteStream,
- AnyByteStreamConnectable,
- ObjectReceiveStream,
- ObjectSendStream,
- ObjectStream,
- ObjectStreamConnectable,
-)
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
-
-@dataclass(eq=False)
-class TextReceiveStream(ObjectReceiveStream[str]):
- """
- Stream wrapper that decodes bytes to strings using the given encoding.
-
- Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any
- completely received unicode characters as soon as they come in.
-
- :param transport_stream: any bytes-based receive stream
- :param encoding: character encoding to use for decoding bytes to strings (defaults
- to ``utf-8``)
- :param errors: handling scheme for decoding errors (defaults to ``strict``; see the
- `codecs module documentation`_ for a comprehensive list of options)
-
- .. _codecs module documentation:
- https://docs.python.org/3/library/codecs.html#codec-objects
- """
-
- transport_stream: AnyByteReceiveStream
- encoding: InitVar[str] = "utf-8"
- errors: InitVar[str] = "strict"
- _decoder: codecs.IncrementalDecoder = field(init=False)
-
- def __post_init__(self, encoding: str, errors: str) -> None:
- decoder_class = codecs.getincrementaldecoder(encoding)
- self._decoder = decoder_class(errors=errors)
-
- async def receive(self) -> str:
- while True:
- chunk = await self.transport_stream.receive()
- decoded = self._decoder.decode(chunk)
- if decoded:
- return decoded
-
- async def aclose(self) -> None:
- await self.transport_stream.aclose()
- self._decoder.reset()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return self.transport_stream.extra_attributes
-
-
-@dataclass(eq=False)
-class TextSendStream(ObjectSendStream[str]):
- """
- Sends strings to the wrapped stream as bytes using the given encoding.
-
- :param AnyByteSendStream transport_stream: any bytes-based send stream
- :param str encoding: character encoding to use for encoding strings to bytes
- (defaults to ``utf-8``)
- :param str errors: handling scheme for encoding errors (defaults to ``strict``; see
- the `codecs module documentation`_ for a comprehensive list of options)
-
- .. _codecs module documentation:
- https://docs.python.org/3/library/codecs.html#codec-objects
- """
-
- transport_stream: AnyByteSendStream
- encoding: InitVar[str] = "utf-8"
- errors: str = "strict"
- _encoder: Callable[..., tuple[bytes, int]] = field(init=False)
-
- def __post_init__(self, encoding: str) -> None:
- self._encoder = codecs.getencoder(encoding)
-
- async def send(self, item: str) -> None:
- encoded = self._encoder(item, self.errors)[0]
- await self.transport_stream.send(encoded)
-
- async def aclose(self) -> None:
- await self.transport_stream.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return self.transport_stream.extra_attributes
-
-
-@dataclass(eq=False)
-class TextStream(ObjectStream[str]):
- """
- A bidirectional stream that decodes bytes to strings on receive and encodes strings
- to bytes on send.
-
- Extra attributes will be provided from both streams, with the receive stream
- providing the values in case of a conflict.
-
- :param AnyByteStream transport_stream: any bytes-based stream
- :param str encoding: character encoding to use for encoding/decoding strings to/from
- bytes (defaults to ``utf-8``)
- :param str errors: handling scheme for encoding errors (defaults to ``strict``; see
- the `codecs module documentation`_ for a comprehensive list of options)
-
- .. _codecs module documentation:
- https://docs.python.org/3/library/codecs.html#codec-objects
- """
-
- transport_stream: AnyByteStream
- encoding: InitVar[str] = "utf-8"
- errors: InitVar[str] = "strict"
- _receive_stream: TextReceiveStream = field(init=False)
- _send_stream: TextSendStream = field(init=False)
-
- def __post_init__(self, encoding: str, errors: str) -> None:
- self._receive_stream = TextReceiveStream(
- self.transport_stream, encoding=encoding, errors=errors
- )
- self._send_stream = TextSendStream(
- self.transport_stream, encoding=encoding, errors=errors
- )
-
- async def receive(self) -> str:
- return await self._receive_stream.receive()
-
- async def send(self, item: str) -> None:
- await self._send_stream.send(item)
-
- async def send_eof(self) -> None:
- await self.transport_stream.send_eof()
-
- async def aclose(self) -> None:
- await self._send_stream.aclose()
- await self._receive_stream.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return {
- **self._send_stream.extra_attributes,
- **self._receive_stream.extra_attributes,
- }
-
-
-class TextConnectable(ObjectStreamConnectable[str]):
- def __init__(self, connectable: AnyByteStreamConnectable):
- """
- :param connectable: the bytestream endpoint to wrap
-
- """
- self.connectable = connectable
-
- @override
- async def connect(self) -> TextStream:
- stream = await self.connectable.connect()
- return TextStream(stream)
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/streams/tls.py b/backend/.venv/lib/python3.12/site-packages/anyio/streams/tls.py
deleted file mode 100644
index b507488..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/streams/tls.py
+++ /dev/null
@@ -1,424 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "TLSAttribute",
- "TLSConnectable",
- "TLSListener",
- "TLSStream",
-)
-
-import logging
-import re
-import ssl
-import sys
-from collections.abc import Callable, Mapping
-from dataclasses import dataclass
-from functools import wraps
-from ssl import SSLContext
-from typing import Any, TypeVar
-
-from .. import (
- BrokenResourceError,
- EndOfStream,
- aclose_forcefully,
- get_cancelled_exc_class,
- to_thread,
-)
-from .._core._typedattr import TypedAttributeSet, typed_attribute
-from ..abc import (
- AnyByteStream,
- AnyByteStreamConnectable,
- ByteStream,
- ByteStreamConnectable,
- Listener,
- TaskGroup,
-)
-
-if sys.version_info >= (3, 10):
- from typing import TypeAlias
-else:
- from typing_extensions import TypeAlias
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-_PCTRTT: TypeAlias = tuple[tuple[str, str], ...]
-_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...]
-
-
-class TLSAttribute(TypedAttributeSet):
- """Contains Transport Layer Security related attributes."""
-
- #: the selected ALPN protocol
- alpn_protocol: str | None = typed_attribute()
- #: the channel binding for type ``tls-unique``
- channel_binding_tls_unique: bytes = typed_attribute()
- #: the selected cipher
- cipher: tuple[str, str, int] = typed_attribute()
- #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert`
- # for more information)
- peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute()
- #: the peer certificate in binary form
- peer_certificate_binary: bytes | None = typed_attribute()
- #: ``True`` if this is the server side of the connection
- server_side: bool = typed_attribute()
- #: ciphers shared by the client during the TLS handshake (``None`` if this is the
- #: client side)
- shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute()
- #: the :class:`~ssl.SSLObject` used for encryption
- ssl_object: ssl.SSLObject = typed_attribute()
- #: ``True`` if this stream does (and expects) a closing TLS handshake when the
- #: stream is being closed
- standard_compatible: bool = typed_attribute()
- #: the TLS protocol version (e.g. ``TLSv1.2``)
- tls_version: str = typed_attribute()
-
-
-@dataclass(eq=False)
-class TLSStream(ByteStream):
- """
- A stream wrapper that encrypts all sent data and decrypts received data.
-
- This class has no public initializer; use :meth:`wrap` instead.
- All extra attributes from :class:`~TLSAttribute` are supported.
-
- :var AnyByteStream transport_stream: the wrapped stream
-
- """
-
- transport_stream: AnyByteStream
- standard_compatible: bool
- _ssl_object: ssl.SSLObject
- _read_bio: ssl.MemoryBIO
- _write_bio: ssl.MemoryBIO
-
- @classmethod
- async def wrap(
- cls,
- transport_stream: AnyByteStream,
- *,
- server_side: bool | None = None,
- hostname: str | None = None,
- ssl_context: ssl.SSLContext | None = None,
- standard_compatible: bool = True,
- ) -> TLSStream:
- """
- Wrap an existing stream with Transport Layer Security.
-
- This performs a TLS handshake with the peer.
-
- :param transport_stream: a bytes-transporting stream to wrap
- :param server_side: ``True`` if this is the server side of the connection,
- ``False`` if this is the client side (if omitted, will be set to ``False``
- if ``hostname`` has been provided, ``False`` otherwise). Used only to create
- a default context when an explicit context has not been provided.
- :param hostname: host name of the peer (if host name checking is desired)
- :param ssl_context: the SSLContext object to use (if not provided, a secure
- default will be created)
- :param standard_compatible: if ``False``, skip the closing handshake when
- closing the connection, and don't raise an exception if the peer does the
- same
- :raises ~ssl.SSLError: if the TLS handshake fails
-
- """
- if server_side is None:
- server_side = not hostname
-
- if not ssl_context:
- purpose = (
- ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH
- )
- ssl_context = ssl.create_default_context(purpose)
-
- # Re-enable detection of unexpected EOFs if it was disabled by Python
- if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"):
- ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF
-
- bio_in = ssl.MemoryBIO()
- bio_out = ssl.MemoryBIO()
-
- # External SSLContext implementations may do blocking I/O in wrap_bio(),
- # but the standard library implementation won't
- if type(ssl_context) is ssl.SSLContext:
- ssl_object = ssl_context.wrap_bio(
- bio_in, bio_out, server_side=server_side, server_hostname=hostname
- )
- else:
- ssl_object = await to_thread.run_sync(
- ssl_context.wrap_bio,
- bio_in,
- bio_out,
- server_side,
- hostname,
- None,
- )
-
- wrapper = cls(
- transport_stream=transport_stream,
- standard_compatible=standard_compatible,
- _ssl_object=ssl_object,
- _read_bio=bio_in,
- _write_bio=bio_out,
- )
- await wrapper._call_sslobject_method(ssl_object.do_handshake)
- return wrapper
-
- async def _call_sslobject_method(
- self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT]
- ) -> T_Retval:
- while True:
- try:
- result = func(*args)
- except ssl.SSLWantReadError:
- try:
- # Flush any pending writes first
- if self._write_bio.pending:
- await self.transport_stream.send(self._write_bio.read())
-
- data = await self.transport_stream.receive()
- except EndOfStream:
- self._read_bio.write_eof()
- except OSError as exc:
- self._read_bio.write_eof()
- self._write_bio.write_eof()
- raise BrokenResourceError from exc
- else:
- self._read_bio.write(data)
- except ssl.SSLWantWriteError:
- await self.transport_stream.send(self._write_bio.read())
- except ssl.SSLSyscallError as exc:
- self._read_bio.write_eof()
- self._write_bio.write_eof()
- raise BrokenResourceError from exc
- except ssl.SSLError as exc:
- self._read_bio.write_eof()
- self._write_bio.write_eof()
- if isinstance(exc, ssl.SSLEOFError) or (
- exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror
- ):
- if self.standard_compatible:
- raise BrokenResourceError from exc
- else:
- raise EndOfStream from None
-
- raise
- else:
- # Flush any pending writes first
- if self._write_bio.pending:
- await self.transport_stream.send(self._write_bio.read())
-
- return result
-
- async def unwrap(self) -> tuple[AnyByteStream, bytes]:
- """
- Does the TLS closing handshake.
-
- :return: a tuple of (wrapped byte stream, bytes left in the read buffer)
-
- """
- await self._call_sslobject_method(self._ssl_object.unwrap)
- self._read_bio.write_eof()
- self._write_bio.write_eof()
- return self.transport_stream, self._read_bio.read()
-
- async def aclose(self) -> None:
- if self.standard_compatible:
- try:
- await self.unwrap()
- except BaseException:
- await aclose_forcefully(self.transport_stream)
- raise
-
- await self.transport_stream.aclose()
-
- async def receive(self, max_bytes: int = 65536) -> bytes:
- data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
- if not data:
- raise EndOfStream
-
- return data
-
- async def send(self, item: bytes) -> None:
- await self._call_sslobject_method(self._ssl_object.write, item)
-
- async def send_eof(self) -> None:
- tls_version = self.extra(TLSAttribute.tls_version)
- match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version)
- if match:
- major, minor = int(match.group(1)), int(match.group(2) or 0)
- if (major, minor) < (1, 3):
- raise NotImplementedError(
- f"send_eof() requires at least TLSv1.3; current "
- f"session uses {tls_version}"
- )
-
- raise NotImplementedError(
- "send_eof() has not yet been implemented for TLS streams"
- )
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return {
- **self.transport_stream.extra_attributes,
- TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol,
- TLSAttribute.channel_binding_tls_unique: (
- self._ssl_object.get_channel_binding
- ),
- TLSAttribute.cipher: self._ssl_object.cipher,
- TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False),
- TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert(
- True
- ),
- TLSAttribute.server_side: lambda: self._ssl_object.server_side,
- TLSAttribute.shared_ciphers: lambda: self._ssl_object.shared_ciphers()
- if self._ssl_object.server_side
- else None,
- TLSAttribute.standard_compatible: lambda: self.standard_compatible,
- TLSAttribute.ssl_object: lambda: self._ssl_object,
- TLSAttribute.tls_version: self._ssl_object.version,
- }
-
-
-@dataclass(eq=False)
-class TLSListener(Listener[TLSStream]):
- """
- A convenience listener that wraps another listener and auto-negotiates a TLS session
- on every accepted connection.
-
- If the TLS handshake times out or raises an exception,
- :meth:`handle_handshake_error` is called to do whatever post-mortem processing is
- deemed necessary.
-
- Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute.
-
- :param Listener listener: the listener to wrap
- :param ssl_context: the SSL context object
- :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap`
- :param handshake_timeout: time limit for the TLS handshake
- (passed to :func:`~anyio.fail_after`)
- """
-
- listener: Listener[Any]
- ssl_context: ssl.SSLContext
- standard_compatible: bool = True
- handshake_timeout: float = 30
-
- @staticmethod
- async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None:
- """
- Handle an exception raised during the TLS handshake.
-
- This method does 3 things:
-
- #. Forcefully closes the original stream
- #. Logs the exception (unless it was a cancellation exception) using the
- ``anyio.streams.tls`` logger
- #. Reraises the exception if it was a base exception or a cancellation exception
-
- :param exc: the exception
- :param stream: the original stream
-
- """
- await aclose_forcefully(stream)
-
- # Log all except cancellation exceptions
- if not isinstance(exc, get_cancelled_exc_class()):
- # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using
- # any asyncio implementation, so we explicitly pass the exception to log
- # (https://github.com/python/cpython/issues/108668). Trio does not have this
- # issue because it works around the CPython bug.
- logging.getLogger(__name__).exception(
- "Error during TLS handshake", exc_info=exc
- )
-
- # Only reraise base exceptions and cancellation exceptions
- if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()):
- raise
-
- async def serve(
- self,
- handler: Callable[[TLSStream], Any],
- task_group: TaskGroup | None = None,
- ) -> None:
- @wraps(handler)
- async def handler_wrapper(stream: AnyByteStream) -> None:
- from .. import fail_after
-
- try:
- with fail_after(self.handshake_timeout):
- wrapped_stream = await TLSStream.wrap(
- stream,
- ssl_context=self.ssl_context,
- standard_compatible=self.standard_compatible,
- )
- except BaseException as exc:
- await self.handle_handshake_error(exc, stream)
- else:
- await handler(wrapped_stream)
-
- await self.listener.serve(handler_wrapper, task_group)
-
- async def aclose(self) -> None:
- await self.listener.aclose()
-
- @property
- def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
- return {
- TLSAttribute.standard_compatible: lambda: self.standard_compatible,
- }
-
-
-class TLSConnectable(ByteStreamConnectable):
- """
- Wraps another connectable and does TLS negotiation after a successful connection.
-
- :param connectable: the connectable to wrap
- :param hostname: host name of the server (if host name checking is desired)
- :param ssl_context: the SSLContext object to use (if not provided, a secure default
- will be created)
- :param standard_compatible: if ``False``, skip the closing handshake when closing
- the connection, and don't raise an exception if the server does the same
- """
-
- def __init__(
- self,
- connectable: AnyByteStreamConnectable,
- *,
- hostname: str | None = None,
- ssl_context: ssl.SSLContext | None = None,
- standard_compatible: bool = True,
- ) -> None:
- self.connectable = connectable
- self.ssl_context: SSLContext = ssl_context or ssl.create_default_context(
- ssl.Purpose.SERVER_AUTH
- )
- if not isinstance(self.ssl_context, ssl.SSLContext):
- raise TypeError(
- "ssl_context must be an instance of ssl.SSLContext, not "
- f"{type(self.ssl_context).__name__}"
- )
- self.hostname = hostname
- self.standard_compatible = standard_compatible
-
- @override
- async def connect(self) -> TLSStream:
- stream = await self.connectable.connect()
- try:
- return await TLSStream.wrap(
- stream,
- hostname=self.hostname,
- ssl_context=self.ssl_context,
- standard_compatible=self.standard_compatible,
- )
- except BaseException:
- await aclose_forcefully(stream)
- raise
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/to_interpreter.py b/backend/.venv/lib/python3.12/site-packages/anyio/to_interpreter.py
deleted file mode 100644
index 694dbe7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/to_interpreter.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "run_sync",
- "current_default_interpreter_limiter",
-)
-
-import atexit
-import os
-import sys
-from collections import deque
-from collections.abc import Callable
-from typing import Any, Final, TypeVar
-
-from . import current_time, to_thread
-from ._core._exceptions import BrokenWorkerInterpreter
-from ._core._synchronization import CapacityLimiter
-from .lowlevel import RunVar
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-if sys.version_info >= (3, 14):
- from concurrent.interpreters import ExecutionFailed, create
-
- def _interp_call(
- func: Callable[..., Any], args: tuple[Any, ...]
- ) -> tuple[Any, bool]:
- try:
- retval = func(*args)
- except BaseException as exc:
- return exc, True
- else:
- return retval, False
-
- class _Worker:
- last_used: float = 0
-
- def __init__(self) -> None:
- self._interpreter = create()
-
- def destroy(self) -> None:
- self._interpreter.close()
-
- def call(
- self,
- func: Callable[..., T_Retval],
- args: tuple[Any, ...],
- ) -> T_Retval:
- try:
- res, is_exception = self._interpreter.call(_interp_call, func, args)
- except ExecutionFailed as exc:
- raise BrokenWorkerInterpreter(exc.excinfo) from exc
-
- if is_exception:
- raise res
-
- return res
-elif sys.version_info >= (3, 13):
- import _interpqueues
- import _interpreters
-
- UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib
- FMT_UNPICKLED: Final = 0
- FMT_PICKLED: Final = 1
- QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND)
- QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND)
-
- _run_func = compile(
- """
-import _interpqueues
-from _interpreters import NotShareableError
-from pickle import loads, dumps, HIGHEST_PROTOCOL
-
-QUEUE_PICKLE_ARGS = (1, 2)
-QUEUE_UNPICKLE_ARGS = (0, 2)
-
-item = _interpqueues.get(queue_id)[0]
-try:
- func, args = loads(item)
- retval = func(*args)
-except BaseException as exc:
- is_exception = True
- retval = exc
-else:
- is_exception = False
-
-try:
- _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS)
-except NotShareableError:
- retval = dumps(retval, HIGHEST_PROTOCOL)
- _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS)
- """,
- "",
- "exec",
- )
-
- class _Worker:
- last_used: float = 0
-
- def __init__(self) -> None:
- self._interpreter_id = _interpreters.create()
- self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS)
- _interpreters.set___main___attrs(
- self._interpreter_id, {"queue_id": self._queue_id}
- )
-
- def destroy(self) -> None:
- _interpqueues.destroy(self._queue_id)
- _interpreters.destroy(self._interpreter_id)
-
- def call(
- self,
- func: Callable[..., T_Retval],
- args: tuple[Any, ...],
- ) -> T_Retval:
- import pickle
-
- item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL)
- _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS)
- exc_info = _interpreters.exec(self._interpreter_id, _run_func)
- if exc_info:
- raise BrokenWorkerInterpreter(exc_info)
-
- res = _interpqueues.get(self._queue_id)
- (res, is_exception), fmt = res[:2]
- if fmt == FMT_PICKLED:
- res = pickle.loads(res)
-
- if is_exception:
- raise res
-
- return res
-else:
-
- class _Worker:
- last_used: float = 0
-
- def __init__(self) -> None:
- raise RuntimeError("subinterpreters require at least Python 3.13")
-
- def call(
- self,
- func: Callable[..., T_Retval],
- args: tuple[Any, ...],
- ) -> T_Retval:
- raise NotImplementedError
-
- def destroy(self) -> None:
- pass
-
-
-DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value
-MAX_WORKER_IDLE_TIME = (
- 30 # seconds a subinterpreter can be idle before becoming eligible for pruning
-)
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-
-_idle_workers = RunVar[deque[_Worker]]("_available_workers")
-_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter")
-
-
-def _stop_workers(workers: deque[_Worker]) -> None:
- for worker in workers:
- worker.destroy()
-
- workers.clear()
-
-
-async def run_sync(
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- *args: Unpack[PosArgsT],
- limiter: CapacityLimiter | None = None,
-) -> T_Retval:
- """
- Call the given function with the given arguments in a subinterpreter.
-
- .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet
- available, so the code path for that Python version relies on an undocumented,
- private API. As such, it is recommended to not rely on this function for anything
- mission-critical on Python 3.13.
-
- :param func: a callable
- :param args: the positional arguments for the callable
- :param limiter: capacity limiter to use to limit the total number of subinterpreters
- running (if omitted, the default limiter is used)
- :return: the result of the call
- :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter
-
- """
- if limiter is None:
- limiter = current_default_interpreter_limiter()
-
- try:
- idle_workers = _idle_workers.get()
- except LookupError:
- idle_workers = deque()
- _idle_workers.set(idle_workers)
- atexit.register(_stop_workers, idle_workers)
-
- async with limiter:
- try:
- worker = idle_workers.pop()
- except IndexError:
- worker = _Worker()
-
- try:
- return await to_thread.run_sync(
- worker.call,
- func,
- args,
- limiter=limiter,
- )
- finally:
- # Prune workers that have been idle for too long
- now = current_time()
- while idle_workers:
- if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME:
- break
-
- await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter)
-
- worker.last_used = current_time()
- idle_workers.append(worker)
-
-
-def current_default_interpreter_limiter() -> CapacityLimiter:
- """
- Return the capacity limiter used by default to limit the number of concurrently
- running subinterpreters.
-
- Defaults to the number of CPU cores.
-
- :return: a capacity limiter object
-
- """
- try:
- return _default_interpreter_limiter.get()
- except LookupError:
- limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT)
- _default_interpreter_limiter.set(limiter)
- return limiter
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/to_process.py b/backend/.venv/lib/python3.12/site-packages/anyio/to_process.py
deleted file mode 100644
index b289234..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/to_process.py
+++ /dev/null
@@ -1,266 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "current_default_process_limiter",
- "process_worker",
- "run_sync",
-)
-
-import os
-import pickle
-import subprocess
-import sys
-from collections import deque
-from collections.abc import Callable
-from importlib.util import module_from_spec, spec_from_file_location
-from typing import TypeVar, cast
-
-from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class
-from ._core._exceptions import BrokenWorkerProcess
-from ._core._subprocesses import open_process
-from ._core._synchronization import CapacityLimiter
-from ._core._tasks import CancelScope, fail_after
-from .abc import ByteReceiveStream, ByteSendStream, Process
-from .lowlevel import RunVar, checkpoint_if_cancelled
-from .streams.buffered import BufferedByteReceiveStream
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-WORKER_MAX_IDLE_TIME = 300 # 5 minutes
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-
-_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers")
-_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar(
- "_process_pool_idle_workers"
-)
-_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter")
-
-
-async def run_sync( # type: ignore[return]
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- *args: Unpack[PosArgsT],
- cancellable: bool = False,
- limiter: CapacityLimiter | None = None,
-) -> T_Retval:
- """
- Call the given function with the given arguments in a worker process.
-
- If the ``cancellable`` option is enabled and the task waiting for its completion is
- cancelled, the worker process running it will be abruptly terminated using SIGKILL
- (or ``terminateProcess()`` on Windows).
-
- :param func: a callable
- :param args: positional arguments for the callable
- :param cancellable: ``True`` to allow cancellation of the operation while it's
- running
- :param limiter: capacity limiter to use to limit the total amount of processes
- running (if omitted, the default limiter is used)
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
- :return: an awaitable that yields the return value of the function.
-
- """
-
- async def send_raw_command(pickled_cmd: bytes) -> object:
- try:
- await stdin.send(pickled_cmd)
- response = await buffered.receive_until(b"\n", 50)
- status, length = response.split(b" ")
- if status not in (b"RETURN", b"EXCEPTION"):
- raise RuntimeError(
- f"Worker process returned unexpected response: {response!r}"
- )
-
- pickled_response = await buffered.receive_exactly(int(length))
- except BaseException as exc:
- workers.discard(process)
- try:
- process.kill()
- with CancelScope(shield=True):
- await process.aclose()
- except ProcessLookupError:
- pass
-
- if isinstance(exc, get_cancelled_exc_class()):
- raise
- else:
- raise BrokenWorkerProcess from exc
-
- retval = pickle.loads(pickled_response)
- if status == b"EXCEPTION":
- assert isinstance(retval, BaseException)
- raise retval
- else:
- return retval
-
- # First pickle the request before trying to reserve a worker process
- await checkpoint_if_cancelled()
- request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL)
-
- # If this is the first run in this event loop thread, set up the necessary variables
- try:
- workers = _process_pool_workers.get()
- idle_workers = _process_pool_idle_workers.get()
- except LookupError:
- workers = set()
- idle_workers = deque()
- _process_pool_workers.set(workers)
- _process_pool_idle_workers.set(idle_workers)
- get_async_backend().setup_process_pool_exit_at_shutdown(workers)
-
- async with limiter or current_default_process_limiter():
- # Pop processes from the pool (starting from the most recently used) until we
- # find one that hasn't exited yet
- process: Process
- while idle_workers:
- process, idle_since = idle_workers.pop()
- if process.returncode is None:
- stdin = cast(ByteSendStream, process.stdin)
- buffered = BufferedByteReceiveStream(
- cast(ByteReceiveStream, process.stdout)
- )
-
- # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME
- # seconds or longer
- now = current_time()
- killed_processes: list[Process] = []
- while idle_workers:
- if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME:
- break
-
- process_to_kill, idle_since = idle_workers.popleft()
- process_to_kill.kill()
- workers.remove(process_to_kill)
- killed_processes.append(process_to_kill)
-
- with CancelScope(shield=True):
- for killed_process in killed_processes:
- await killed_process.aclose()
-
- break
-
- workers.remove(process)
- else:
- command = [sys.executable, "-u", "-m", __name__]
- process = await open_process(
- command, stdin=subprocess.PIPE, stdout=subprocess.PIPE
- )
- try:
- stdin = cast(ByteSendStream, process.stdin)
- buffered = BufferedByteReceiveStream(
- cast(ByteReceiveStream, process.stdout)
- )
- with fail_after(20):
- message = await buffered.receive(6)
-
- if message != b"READY\n":
- raise BrokenWorkerProcess(
- f"Worker process returned unexpected response: {message!r}"
- )
-
- main_module_path = getattr(sys.modules["__main__"], "__file__", None)
- pickled = pickle.dumps(
- ("init", sys.path, main_module_path),
- protocol=pickle.HIGHEST_PROTOCOL,
- )
- await send_raw_command(pickled)
- except (BrokenWorkerProcess, get_cancelled_exc_class()):
- raise
- except BaseException as exc:
- process.kill()
- raise BrokenWorkerProcess(
- "Error during worker process initialization"
- ) from exc
-
- workers.add(process)
-
- with CancelScope(shield=not cancellable):
- try:
- return cast(T_Retval, await send_raw_command(request))
- finally:
- if process in workers:
- idle_workers.append((process, current_time()))
-
-
-def current_default_process_limiter() -> CapacityLimiter:
- """
- Return the capacity limiter that is used by default to limit the number of worker
- processes.
-
- :return: a capacity limiter object
-
- """
- try:
- return _default_process_limiter.get()
- except LookupError:
- limiter = CapacityLimiter(os.cpu_count() or 2)
- _default_process_limiter.set(limiter)
- return limiter
-
-
-def process_worker() -> None:
- # Redirect standard streams to os.devnull so that user code won't interfere with the
- # parent-worker communication
- stdin = sys.stdin
- stdout = sys.stdout
- sys.stdin = open(os.devnull)
- sys.stdout = open(os.devnull, "w")
-
- stdout.buffer.write(b"READY\n")
- while True:
- retval = exception = None
- try:
- command, *args = pickle.load(stdin.buffer)
- except EOFError:
- return
- except BaseException as exc:
- exception = exc
- else:
- if command == "run":
- func, args = args
- try:
- retval = func(*args)
- except BaseException as exc:
- exception = exc
- elif command == "init":
- main_module_path: str | None
- sys.path, main_module_path = args
- del sys.modules["__main__"]
- if main_module_path and os.path.isfile(main_module_path):
- # Load the parent's main module but as __mp_main__ instead of
- # __main__ (like multiprocessing does) to avoid infinite recursion
- try:
- spec = spec_from_file_location("__mp_main__", main_module_path)
- if spec and spec.loader:
- main = module_from_spec(spec)
- spec.loader.exec_module(main)
- sys.modules["__main__"] = main
- except BaseException as exc:
- exception = exc
- try:
- if exception is not None:
- status = b"EXCEPTION"
- pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL)
- else:
- status = b"RETURN"
- pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL)
- except BaseException as exc:
- exception = exc
- status = b"EXCEPTION"
- pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL)
-
- stdout.buffer.write(b"%s %d\n" % (status, len(pickled)))
- stdout.buffer.write(pickled)
-
- # Respect SIGTERM
- if isinstance(exception, SystemExit):
- raise exception
-
-
-if __name__ == "__main__":
- process_worker()
diff --git a/backend/.venv/lib/python3.12/site-packages/anyio/to_thread.py b/backend/.venv/lib/python3.12/site-packages/anyio/to_thread.py
deleted file mode 100644
index 4be5b71..0000000
--- a/backend/.venv/lib/python3.12/site-packages/anyio/to_thread.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from __future__ import annotations
-
-__all__ = (
- "run_sync",
- "current_default_thread_limiter",
-)
-
-import sys
-from collections.abc import Callable
-from typing import TypeVar
-from warnings import warn
-
-from ._core._eventloop import get_async_backend
-from .abc import CapacityLimiter
-
-if sys.version_info >= (3, 11):
- from typing import TypeVarTuple, Unpack
-else:
- from typing_extensions import TypeVarTuple, Unpack
-
-T_Retval = TypeVar("T_Retval")
-PosArgsT = TypeVarTuple("PosArgsT")
-
-
-async def run_sync(
- func: Callable[[Unpack[PosArgsT]], T_Retval],
- *args: Unpack[PosArgsT],
- abandon_on_cancel: bool = False,
- cancellable: bool | None = None,
- limiter: CapacityLimiter | None = None,
-) -> T_Retval:
- """
- Call the given function with the given arguments in a worker thread.
-
- If the ``cancellable`` option is enabled and the task waiting for its completion is
- cancelled, the thread will still run its course but its return value (or any raised
- exception) will be ignored.
-
- :param func: a callable
- :param args: positional arguments for the callable
- :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run
- unchecked on own) if the host task is cancelled, ``False`` to ignore
- cancellations in the host task until the operation has completed in the worker
- thread
- :param cancellable: deprecated alias of ``abandon_on_cancel``; will override
- ``abandon_on_cancel`` if both parameters are passed
- :param limiter: capacity limiter to use to limit the total amount of threads running
- (if omitted, the default limiter is used)
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
- :return: an awaitable that yields the return value of the function.
-
- """
- if cancellable is not None:
- abandon_on_cancel = cancellable
- warn(
- "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is "
- "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead",
- DeprecationWarning,
- stacklevel=2,
- )
-
- return await get_async_backend().run_sync_in_worker_thread(
- func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter
- )
-
-
-def current_default_thread_limiter() -> CapacityLimiter:
- """
- Return the capacity limiter that is used by default to limit the number of
- concurrent threads.
-
- :return: a capacity limiter object
- :raises NoEventLoopError: if no supported asynchronous event loop is running in the
- current thread
-
- """
- return get_async_backend().current_default_thread_limiter()
diff --git a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/METADATA
deleted file mode 100644
index 3f433af..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/METADATA
+++ /dev/null
@@ -1,84 +0,0 @@
-Metadata-Version: 2.4
-Name: click
-Version: 8.3.1
-Summary: Composable command line interface toolkit
-Maintainer-email: Pallets
-Requires-Python: >=3.10
-Description-Content-Type: text/markdown
-License-Expression: BSD-3-Clause
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Typing :: Typed
-License-File: LICENSE.txt
-Requires-Dist: colorama; platform_system == 'Windows'
-Project-URL: Changes, https://click.palletsprojects.com/page/changes/
-Project-URL: Chat, https://discord.gg/pallets
-Project-URL: Documentation, https://click.palletsprojects.com/
-Project-URL: Donate, https://palletsprojects.com/donate
-Project-URL: Source, https://github.com/pallets/click/
-
-
-
-# Click
-
-Click is a Python package for creating beautiful command line interfaces
-in a composable way with as little code as necessary. It's the "Command
-Line Interface Creation Kit". It's highly configurable but comes with
-sensible defaults out of the box.
-
-It aims to make the process of writing command line tools quick and fun
-while also preventing any frustration caused by the inability to
-implement an intended CLI API.
-
-Click in three points:
-
-- Arbitrary nesting of commands
-- Automatic help page generation
-- Supports lazy loading of subcommands at runtime
-
-
-## A Simple Example
-
-```python
-import click
-
-@click.command()
-@click.option("--count", default=1, help="Number of greetings.")
-@click.option("--name", prompt="Your name", help="The person to greet.")
-def hello(count, name):
- """Simple program that greets NAME for a total of COUNT times."""
- for _ in range(count):
- click.echo(f"Hello, {name}!")
-
-if __name__ == '__main__':
- hello()
-```
-
-```
-$ python hello.py --count=3
-Your name: Click
-Hello, Click!
-Hello, Click!
-Hello, Click!
-```
-
-
-## Donate
-
-The Pallets organization develops and supports Click and other popular
-packages. In order to grow the community of contributors and users, and
-allow the maintainers to devote more time to the projects, [please
-donate today][].
-
-[please donate today]: https://palletsprojects.com/donate
-
-## Contributing
-
-See our [detailed contributing documentation][contrib] for many ways to
-contribute, including reporting issues, requesting features, asking or answering
-questions, and making PRs.
-
-[contrib]: https://palletsprojects.com/contributing/
-
diff --git a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/RECORD
deleted file mode 100644
index 008d697..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/RECORD
+++ /dev/null
@@ -1,40 +0,0 @@
-click-8.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-click-8.3.1.dist-info/METADATA,sha256=XZeBrMAE0ghTE88SjfrSDuSyNCpBPplxJR1tbwD9oZg,2621
-click-8.3.1.dist-info/RECORD,,
-click-8.3.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
-click-8.3.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
-click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
-click/__pycache__/__init__.cpython-312.pyc,,
-click/__pycache__/_compat.cpython-312.pyc,,
-click/__pycache__/_termui_impl.cpython-312.pyc,,
-click/__pycache__/_textwrap.cpython-312.pyc,,
-click/__pycache__/_utils.cpython-312.pyc,,
-click/__pycache__/_winconsole.cpython-312.pyc,,
-click/__pycache__/core.cpython-312.pyc,,
-click/__pycache__/decorators.cpython-312.pyc,,
-click/__pycache__/exceptions.cpython-312.pyc,,
-click/__pycache__/formatting.cpython-312.pyc,,
-click/__pycache__/globals.cpython-312.pyc,,
-click/__pycache__/parser.cpython-312.pyc,,
-click/__pycache__/shell_completion.cpython-312.pyc,,
-click/__pycache__/termui.cpython-312.pyc,,
-click/__pycache__/testing.cpython-312.pyc,,
-click/__pycache__/types.cpython-312.pyc,,
-click/__pycache__/utils.cpython-312.pyc,,
-click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
-click/_termui_impl.py,sha256=rgCb3On8X5A4200rA5L6i13u5iapmFer7sru57Jy6zA,27093
-click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
-click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943
-click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
-click/core.py,sha256=U6Bfxt8GkjNDqyJ0HqXvluJHtyZ4sY5USAvM1Cdq7mQ,132105
-click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
-click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954
-click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730
-click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
-click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010
-click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994
-click/termui.py,sha256=hqCEjNndU-nzW08nRAkBaVgfZp_FdCA9KxfIWlKYaMc,31037
-click/testing.py,sha256=EERbzcl1br0mW0qBS9EqkknfNfXB9WQEW0ELIpkvuSs,19102
-click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927
-click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257
diff --git a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/WHEEL
deleted file mode 100644
index d8b9936..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: flit 3.12.0
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt b/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt
deleted file mode 100644
index d12a849..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-Copyright 2014 Pallets
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
-TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__init__.py b/backend/.venv/lib/python3.12/site-packages/click/__init__.py
deleted file mode 100644
index 1aa547c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/__init__.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""
-Click is a simple Python module inspired by the stdlib optparse to make
-writing command line scripts fun. Unlike other modules, it's based
-around a simple API that does not come with too much magic and is
-composable.
-"""
-
-from __future__ import annotations
-
-from .core import Argument as Argument
-from .core import Command as Command
-from .core import CommandCollection as CommandCollection
-from .core import Context as Context
-from .core import Group as Group
-from .core import Option as Option
-from .core import Parameter as Parameter
-from .decorators import argument as argument
-from .decorators import command as command
-from .decorators import confirmation_option as confirmation_option
-from .decorators import group as group
-from .decorators import help_option as help_option
-from .decorators import make_pass_decorator as make_pass_decorator
-from .decorators import option as option
-from .decorators import pass_context as pass_context
-from .decorators import pass_obj as pass_obj
-from .decorators import password_option as password_option
-from .decorators import version_option as version_option
-from .exceptions import Abort as Abort
-from .exceptions import BadArgumentUsage as BadArgumentUsage
-from .exceptions import BadOptionUsage as BadOptionUsage
-from .exceptions import BadParameter as BadParameter
-from .exceptions import ClickException as ClickException
-from .exceptions import FileError as FileError
-from .exceptions import MissingParameter as MissingParameter
-from .exceptions import NoSuchOption as NoSuchOption
-from .exceptions import UsageError as UsageError
-from .formatting import HelpFormatter as HelpFormatter
-from .formatting import wrap_text as wrap_text
-from .globals import get_current_context as get_current_context
-from .termui import clear as clear
-from .termui import confirm as confirm
-from .termui import echo_via_pager as echo_via_pager
-from .termui import edit as edit
-from .termui import getchar as getchar
-from .termui import launch as launch
-from .termui import pause as pause
-from .termui import progressbar as progressbar
-from .termui import prompt as prompt
-from .termui import secho as secho
-from .termui import style as style
-from .termui import unstyle as unstyle
-from .types import BOOL as BOOL
-from .types import Choice as Choice
-from .types import DateTime as DateTime
-from .types import File as File
-from .types import FLOAT as FLOAT
-from .types import FloatRange as FloatRange
-from .types import INT as INT
-from .types import IntRange as IntRange
-from .types import ParamType as ParamType
-from .types import Path as Path
-from .types import STRING as STRING
-from .types import Tuple as Tuple
-from .types import UNPROCESSED as UNPROCESSED
-from .types import UUID as UUID
-from .utils import echo as echo
-from .utils import format_filename as format_filename
-from .utils import get_app_dir as get_app_dir
-from .utils import get_binary_stream as get_binary_stream
-from .utils import get_text_stream as get_text_stream
-from .utils import open_file as open_file
-
-
-def __getattr__(name: str) -> object:
- import warnings
-
- if name == "BaseCommand":
- from .core import _BaseCommand
-
- warnings.warn(
- "'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
- " 'Command' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _BaseCommand
-
- if name == "MultiCommand":
- from .core import _MultiCommand
-
- warnings.warn(
- "'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
- " 'Group' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _MultiCommand
-
- if name == "OptionParser":
- from .parser import _OptionParser
-
- warnings.warn(
- "'OptionParser' is deprecated and will be removed in Click 9.0. The"
- " old parser is available in 'optparse'.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _OptionParser
-
- if name == "__version__":
- import importlib.metadata
- import warnings
-
- warnings.warn(
- "The '__version__' attribute is deprecated and will be removed in"
- " Click 9.1. Use feature detection or"
- " 'importlib.metadata.version(\"click\")' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return importlib.metadata.version("click")
-
- raise AttributeError(name)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index aadcbff..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc
deleted file mode 100644
index a44ecbd..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc
deleted file mode 100644
index 7fdfa2e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc
deleted file mode 100644
index d7843a9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc
deleted file mode 100644
index 081a35d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc
deleted file mode 100644
index 83acf06..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc
deleted file mode 100644
index 8619837..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc
deleted file mode 100644
index 9dfe0f1..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc
deleted file mode 100644
index e4b9c68..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc
deleted file mode 100644
index 841b158..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc
deleted file mode 100644
index ee84323..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc
deleted file mode 100644
index 91ddd72..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc
deleted file mode 100644
index 735b078..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc
deleted file mode 100644
index 1b3fd7a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc
deleted file mode 100644
index 6bca7a1..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc
deleted file mode 100644
index 05f0778..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc
deleted file mode 100644
index 4070b9e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/click/_compat.py b/backend/.venv/lib/python3.12/site-packages/click/_compat.py
deleted file mode 100644
index f2726b9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/_compat.py
+++ /dev/null
@@ -1,622 +0,0 @@
-from __future__ import annotations
-
-import codecs
-import collections.abc as cabc
-import io
-import os
-import re
-import sys
-import typing as t
-from types import TracebackType
-from weakref import WeakKeyDictionary
-
-CYGWIN = sys.platform.startswith("cygwin")
-WIN = sys.platform.startswith("win")
-auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None
-_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
-
-
-def _make_text_stream(
- stream: t.BinaryIO,
- encoding: str | None,
- errors: str | None,
- force_readable: bool = False,
- force_writable: bool = False,
-) -> t.TextIO:
- if encoding is None:
- encoding = get_best_encoding(stream)
- if errors is None:
- errors = "replace"
- return _NonClosingTextIOWrapper(
- stream,
- encoding,
- errors,
- line_buffering=True,
- force_readable=force_readable,
- force_writable=force_writable,
- )
-
-
-def is_ascii_encoding(encoding: str) -> bool:
- """Checks if a given encoding is ascii."""
- try:
- return codecs.lookup(encoding).name == "ascii"
- except LookupError:
- return False
-
-
-def get_best_encoding(stream: t.IO[t.Any]) -> str:
- """Returns the default stream encoding if not found."""
- rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
- if is_ascii_encoding(rv):
- return "utf-8"
- return rv
-
-
-class _NonClosingTextIOWrapper(io.TextIOWrapper):
- def __init__(
- self,
- stream: t.BinaryIO,
- encoding: str | None,
- errors: str | None,
- force_readable: bool = False,
- force_writable: bool = False,
- **extra: t.Any,
- ) -> None:
- self._stream = stream = t.cast(
- t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
- )
- super().__init__(stream, encoding, errors, **extra)
-
- def __del__(self) -> None:
- try:
- self.detach()
- except Exception:
- pass
-
- def isatty(self) -> bool:
- # https://bitbucket.org/pypy/pypy/issue/1803
- return self._stream.isatty()
-
-
-class _FixupStream:
- """The new io interface needs more from streams than streams
- traditionally implement. As such, this fix-up code is necessary in
- some circumstances.
-
- The forcing of readable and writable flags are there because some tools
- put badly patched objects on sys (one such offender are certain version
- of jupyter notebook).
- """
-
- def __init__(
- self,
- stream: t.BinaryIO,
- force_readable: bool = False,
- force_writable: bool = False,
- ):
- self._stream = stream
- self._force_readable = force_readable
- self._force_writable = force_writable
-
- def __getattr__(self, name: str) -> t.Any:
- return getattr(self._stream, name)
-
- def read1(self, size: int) -> bytes:
- f = getattr(self._stream, "read1", None)
-
- if f is not None:
- return t.cast(bytes, f(size))
-
- return self._stream.read(size)
-
- def readable(self) -> bool:
- if self._force_readable:
- return True
- x = getattr(self._stream, "readable", None)
- if x is not None:
- return t.cast(bool, x())
- try:
- self._stream.read(0)
- except Exception:
- return False
- return True
-
- def writable(self) -> bool:
- if self._force_writable:
- return True
- x = getattr(self._stream, "writable", None)
- if x is not None:
- return t.cast(bool, x())
- try:
- self._stream.write(b"")
- except Exception:
- try:
- self._stream.write(b"")
- except Exception:
- return False
- return True
-
- def seekable(self) -> bool:
- x = getattr(self._stream, "seekable", None)
- if x is not None:
- return t.cast(bool, x())
- try:
- self._stream.seek(self._stream.tell())
- except Exception:
- return False
- return True
-
-
-def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
- try:
- return isinstance(stream.read(0), bytes)
- except Exception:
- return default
- # This happens in some cases where the stream was already
- # closed. In this case, we assume the default.
-
-
-def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool:
- try:
- stream.write(b"")
- except Exception:
- try:
- stream.write("")
- return False
- except Exception:
- pass
- return default
- return True
-
-
-def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None:
- # We need to figure out if the given stream is already binary.
- # This can happen because the official docs recommend detaching
- # the streams to get binary streams. Some code might do this, so
- # we need to deal with this case explicitly.
- if _is_binary_reader(stream, False):
- return t.cast(t.BinaryIO, stream)
-
- buf = getattr(stream, "buffer", None)
-
- # Same situation here; this time we assume that the buffer is
- # actually binary in case it's closed.
- if buf is not None and _is_binary_reader(buf, True):
- return t.cast(t.BinaryIO, buf)
-
- return None
-
-
-def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None:
- # We need to figure out if the given stream is already binary.
- # This can happen because the official docs recommend detaching
- # the streams to get binary streams. Some code might do this, so
- # we need to deal with this case explicitly.
- if _is_binary_writer(stream, False):
- return t.cast(t.BinaryIO, stream)
-
- buf = getattr(stream, "buffer", None)
-
- # Same situation here; this time we assume that the buffer is
- # actually binary in case it's closed.
- if buf is not None and _is_binary_writer(buf, True):
- return t.cast(t.BinaryIO, buf)
-
- return None
-
-
-def _stream_is_misconfigured(stream: t.TextIO) -> bool:
- """A stream is misconfigured if its encoding is ASCII."""
- # If the stream does not have an encoding set, we assume it's set
- # to ASCII. This appears to happen in certain unittest
- # environments. It's not quite clear what the correct behavior is
- # but this at least will force Click to recover somehow.
- return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
-
-
-def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool:
- """A stream attribute is compatible if it is equal to the
- desired value or the desired value is unset and the attribute
- has a value.
- """
- stream_value = getattr(stream, attr, None)
- return stream_value == value or (value is None and stream_value is not None)
-
-
-def _is_compatible_text_stream(
- stream: t.TextIO, encoding: str | None, errors: str | None
-) -> bool:
- """Check if a stream's encoding and errors attributes are
- compatible with the desired values.
- """
- return _is_compat_stream_attr(
- stream, "encoding", encoding
- ) and _is_compat_stream_attr(stream, "errors", errors)
-
-
-def _force_correct_text_stream(
- text_stream: t.IO[t.Any],
- encoding: str | None,
- errors: str | None,
- is_binary: t.Callable[[t.IO[t.Any], bool], bool],
- find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None],
- force_readable: bool = False,
- force_writable: bool = False,
-) -> t.TextIO:
- if is_binary(text_stream, False):
- binary_reader = t.cast(t.BinaryIO, text_stream)
- else:
- text_stream = t.cast(t.TextIO, text_stream)
- # If the stream looks compatible, and won't default to a
- # misconfigured ascii encoding, return it as-is.
- if _is_compatible_text_stream(text_stream, encoding, errors) and not (
- encoding is None and _stream_is_misconfigured(text_stream)
- ):
- return text_stream
-
- # Otherwise, get the underlying binary reader.
- possible_binary_reader = find_binary(text_stream)
-
- # If that's not possible, silently use the original reader
- # and get mojibake instead of exceptions.
- if possible_binary_reader is None:
- return text_stream
-
- binary_reader = possible_binary_reader
-
- # Default errors to replace instead of strict in order to get
- # something that works.
- if errors is None:
- errors = "replace"
-
- # Wrap the binary stream in a text stream with the correct
- # encoding parameters.
- return _make_text_stream(
- binary_reader,
- encoding,
- errors,
- force_readable=force_readable,
- force_writable=force_writable,
- )
-
-
-def _force_correct_text_reader(
- text_reader: t.IO[t.Any],
- encoding: str | None,
- errors: str | None,
- force_readable: bool = False,
-) -> t.TextIO:
- return _force_correct_text_stream(
- text_reader,
- encoding,
- errors,
- _is_binary_reader,
- _find_binary_reader,
- force_readable=force_readable,
- )
-
-
-def _force_correct_text_writer(
- text_writer: t.IO[t.Any],
- encoding: str | None,
- errors: str | None,
- force_writable: bool = False,
-) -> t.TextIO:
- return _force_correct_text_stream(
- text_writer,
- encoding,
- errors,
- _is_binary_writer,
- _find_binary_writer,
- force_writable=force_writable,
- )
-
-
-def get_binary_stdin() -> t.BinaryIO:
- reader = _find_binary_reader(sys.stdin)
- if reader is None:
- raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
- return reader
-
-
-def get_binary_stdout() -> t.BinaryIO:
- writer = _find_binary_writer(sys.stdout)
- if writer is None:
- raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
- return writer
-
-
-def get_binary_stderr() -> t.BinaryIO:
- writer = _find_binary_writer(sys.stderr)
- if writer is None:
- raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
- return writer
-
-
-def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
- rv = _get_windows_console_stream(sys.stdin, encoding, errors)
- if rv is not None:
- return rv
- return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
-
-
-def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
- rv = _get_windows_console_stream(sys.stdout, encoding, errors)
- if rv is not None:
- return rv
- return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
-
-
-def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
- rv = _get_windows_console_stream(sys.stderr, encoding, errors)
- if rv is not None:
- return rv
- return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
-
-
-def _wrap_io_open(
- file: str | os.PathLike[str] | int,
- mode: str,
- encoding: str | None,
- errors: str | None,
-) -> t.IO[t.Any]:
- """Handles not passing ``encoding`` and ``errors`` in binary mode."""
- if "b" in mode:
- return open(file, mode)
-
- return open(file, mode, encoding=encoding, errors=errors)
-
-
-def open_stream(
- filename: str | os.PathLike[str],
- mode: str = "r",
- encoding: str | None = None,
- errors: str | None = "strict",
- atomic: bool = False,
-) -> tuple[t.IO[t.Any], bool]:
- binary = "b" in mode
- filename = os.fspath(filename)
-
- # Standard streams first. These are simple because they ignore the
- # atomic flag. Use fsdecode to handle Path("-").
- if os.fsdecode(filename) == "-":
- if any(m in mode for m in ["w", "a", "x"]):
- if binary:
- return get_binary_stdout(), False
- return get_text_stdout(encoding=encoding, errors=errors), False
- if binary:
- return get_binary_stdin(), False
- return get_text_stdin(encoding=encoding, errors=errors), False
-
- # Non-atomic writes directly go out through the regular open functions.
- if not atomic:
- return _wrap_io_open(filename, mode, encoding, errors), True
-
- # Some usability stuff for atomic writes
- if "a" in mode:
- raise ValueError(
- "Appending to an existing file is not supported, because that"
- " would involve an expensive `copy`-operation to a temporary"
- " file. Open the file in normal `w`-mode and copy explicitly"
- " if that's what you're after."
- )
- if "x" in mode:
- raise ValueError("Use the `overwrite`-parameter instead.")
- if "w" not in mode:
- raise ValueError("Atomic writes only make sense with `w`-mode.")
-
- # Atomic writes are more complicated. They work by opening a file
- # as a proxy in the same folder and then using the fdopen
- # functionality to wrap it in a Python file. Then we wrap it in an
- # atomic file that moves the file over on close.
- import errno
- import random
-
- try:
- perm: int | None = os.stat(filename).st_mode
- except OSError:
- perm = None
-
- flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
-
- if binary:
- flags |= getattr(os, "O_BINARY", 0)
-
- while True:
- tmp_filename = os.path.join(
- os.path.dirname(filename),
- f".__atomic-write{random.randrange(1 << 32):08x}",
- )
- try:
- fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
- break
- except OSError as e:
- if e.errno == errno.EEXIST or (
- os.name == "nt"
- and e.errno == errno.EACCES
- and os.path.isdir(e.filename)
- and os.access(e.filename, os.W_OK)
- ):
- continue
- raise
-
- if perm is not None:
- os.chmod(tmp_filename, perm) # in case perm includes bits in umask
-
- f = _wrap_io_open(fd, mode, encoding, errors)
- af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
- return t.cast(t.IO[t.Any], af), True
-
-
-class _AtomicFile:
- def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
- self._f = f
- self._tmp_filename = tmp_filename
- self._real_filename = real_filename
- self.closed = False
-
- @property
- def name(self) -> str:
- return self._real_filename
-
- def close(self, delete: bool = False) -> None:
- if self.closed:
- return
- self._f.close()
- os.replace(self._tmp_filename, self._real_filename)
- self.closed = True
-
- def __getattr__(self, name: str) -> t.Any:
- return getattr(self._f, name)
-
- def __enter__(self) -> _AtomicFile:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> None:
- self.close(delete=exc_type is not None)
-
- def __repr__(self) -> str:
- return repr(self._f)
-
-
-def strip_ansi(value: str) -> str:
- return _ansi_re.sub("", value)
-
-
-def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
- while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
- stream = stream._stream
-
- return stream.__class__.__module__.startswith("ipykernel.")
-
-
-def should_strip_ansi(
- stream: t.IO[t.Any] | None = None, color: bool | None = None
-) -> bool:
- if color is None:
- if stream is None:
- stream = sys.stdin
- return not isatty(stream) and not _is_jupyter_kernel_output(stream)
- return not color
-
-
-# On Windows, wrap the output streams with colorama to support ANSI
-# color codes.
-# NOTE: double check is needed so mypy does not analyze this on Linux
-if sys.platform.startswith("win") and WIN:
- from ._winconsole import _get_windows_console_stream
-
- def _get_argv_encoding() -> str:
- import locale
-
- return locale.getpreferredencoding()
-
- _ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
-
- def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO:
- """Support ANSI color and style codes on Windows by wrapping a
- stream with colorama.
- """
- try:
- cached = _ansi_stream_wrappers.get(stream)
- except Exception:
- cached = None
-
- if cached is not None:
- return cached
-
- import colorama
-
- strip = should_strip_ansi(stream, color)
- ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
- rv = t.cast(t.TextIO, ansi_wrapper.stream)
- _write = rv.write
-
- def _safe_write(s: str) -> int:
- try:
- return _write(s)
- except BaseException:
- ansi_wrapper.reset_all()
- raise
-
- rv.write = _safe_write # type: ignore[method-assign]
-
- try:
- _ansi_stream_wrappers[stream] = rv
- except Exception:
- pass
-
- return rv
-
-else:
-
- def _get_argv_encoding() -> str:
- return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
-
- def _get_windows_console_stream(
- f: t.TextIO, encoding: str | None, errors: str | None
- ) -> t.TextIO | None:
- return None
-
-
-def term_len(x: str) -> int:
- return len(strip_ansi(x))
-
-
-def isatty(stream: t.IO[t.Any]) -> bool:
- try:
- return stream.isatty()
- except Exception:
- return False
-
-
-def _make_cached_stream_func(
- src_func: t.Callable[[], t.TextIO | None],
- wrapper_func: t.Callable[[], t.TextIO],
-) -> t.Callable[[], t.TextIO | None]:
- cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
-
- def func() -> t.TextIO | None:
- stream = src_func()
-
- if stream is None:
- return None
-
- try:
- rv = cache.get(stream)
- except Exception:
- rv = None
- if rv is not None:
- return rv
- rv = wrapper_func()
- try:
- cache[stream] = rv
- except Exception:
- pass
- return rv
-
- return func
-
-
-_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
-_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
-_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
-
-
-binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = {
- "stdin": get_binary_stdin,
- "stdout": get_binary_stdout,
- "stderr": get_binary_stderr,
-}
-
-text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = {
- "stdin": get_text_stdin,
- "stdout": get_text_stdout,
- "stderr": get_text_stderr,
-}
diff --git a/backend/.venv/lib/python3.12/site-packages/click/_termui_impl.py b/backend/.venv/lib/python3.12/site-packages/click/_termui_impl.py
deleted file mode 100644
index ee8225c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/_termui_impl.py
+++ /dev/null
@@ -1,852 +0,0 @@
-"""
-This module contains implementations for the termui module. To keep the
-import time of Click down, some infrequently used functionality is
-placed in this module and only imported as needed.
-"""
-
-from __future__ import annotations
-
-import collections.abc as cabc
-import contextlib
-import math
-import os
-import shlex
-import sys
-import time
-import typing as t
-from gettext import gettext as _
-from io import StringIO
-from pathlib import Path
-from types import TracebackType
-
-from ._compat import _default_text_stdout
-from ._compat import CYGWIN
-from ._compat import get_best_encoding
-from ._compat import isatty
-from ._compat import open_stream
-from ._compat import strip_ansi
-from ._compat import term_len
-from ._compat import WIN
-from .exceptions import ClickException
-from .utils import echo
-
-V = t.TypeVar("V")
-
-if os.name == "nt":
- BEFORE_BAR = "\r"
- AFTER_BAR = "\n"
-else:
- BEFORE_BAR = "\r\033[?25l"
- AFTER_BAR = "\033[?25h\n"
-
-
-class ProgressBar(t.Generic[V]):
- def __init__(
- self,
- iterable: cabc.Iterable[V] | None,
- length: int | None = None,
- fill_char: str = "#",
- empty_char: str = " ",
- bar_template: str = "%(bar)s",
- info_sep: str = " ",
- hidden: bool = False,
- show_eta: bool = True,
- show_percent: bool | None = None,
- show_pos: bool = False,
- item_show_func: t.Callable[[V | None], str | None] | None = None,
- label: str | None = None,
- file: t.TextIO | None = None,
- color: bool | None = None,
- update_min_steps: int = 1,
- width: int = 30,
- ) -> None:
- self.fill_char = fill_char
- self.empty_char = empty_char
- self.bar_template = bar_template
- self.info_sep = info_sep
- self.hidden = hidden
- self.show_eta = show_eta
- self.show_percent = show_percent
- self.show_pos = show_pos
- self.item_show_func = item_show_func
- self.label: str = label or ""
-
- if file is None:
- file = _default_text_stdout()
-
- # There are no standard streams attached to write to. For example,
- # pythonw on Windows.
- if file is None:
- file = StringIO()
-
- self.file = file
- self.color = color
- self.update_min_steps = update_min_steps
- self._completed_intervals = 0
- self.width: int = width
- self.autowidth: bool = width == 0
-
- if length is None:
- from operator import length_hint
-
- length = length_hint(iterable, -1)
-
- if length == -1:
- length = None
- if iterable is None:
- if length is None:
- raise TypeError("iterable or length is required")
- iterable = t.cast("cabc.Iterable[V]", range(length))
- self.iter: cabc.Iterable[V] = iter(iterable)
- self.length = length
- self.pos: int = 0
- self.avg: list[float] = []
- self.last_eta: float
- self.start: float
- self.start = self.last_eta = time.time()
- self.eta_known: bool = False
- self.finished: bool = False
- self.max_width: int | None = None
- self.entered: bool = False
- self.current_item: V | None = None
- self._is_atty = isatty(self.file)
- self._last_line: str | None = None
-
- def __enter__(self) -> ProgressBar[V]:
- self.entered = True
- self.render_progress()
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> None:
- self.render_finish()
-
- def __iter__(self) -> cabc.Iterator[V]:
- if not self.entered:
- raise RuntimeError("You need to use progress bars in a with block.")
- self.render_progress()
- return self.generator()
-
- def __next__(self) -> V:
- # Iteration is defined in terms of a generator function,
- # returned by iter(self); use that to define next(). This works
- # because `self.iter` is an iterable consumed by that generator,
- # so it is re-entry safe. Calling `next(self.generator())`
- # twice works and does "what you want".
- return next(iter(self))
-
- def render_finish(self) -> None:
- if self.hidden or not self._is_atty:
- return
- self.file.write(AFTER_BAR)
- self.file.flush()
-
- @property
- def pct(self) -> float:
- if self.finished:
- return 1.0
- return min(self.pos / (float(self.length or 1) or 1), 1.0)
-
- @property
- def time_per_iteration(self) -> float:
- if not self.avg:
- return 0.0
- return sum(self.avg) / float(len(self.avg))
-
- @property
- def eta(self) -> float:
- if self.length is not None and not self.finished:
- return self.time_per_iteration * (self.length - self.pos)
- return 0.0
-
- def format_eta(self) -> str:
- if self.eta_known:
- t = int(self.eta)
- seconds = t % 60
- t //= 60
- minutes = t % 60
- t //= 60
- hours = t % 24
- t //= 24
- if t > 0:
- return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
- else:
- return f"{hours:02}:{minutes:02}:{seconds:02}"
- return ""
-
- def format_pos(self) -> str:
- pos = str(self.pos)
- if self.length is not None:
- pos += f"/{self.length}"
- return pos
-
- def format_pct(self) -> str:
- return f"{int(self.pct * 100): 4}%"[1:]
-
- def format_bar(self) -> str:
- if self.length is not None:
- bar_length = int(self.pct * self.width)
- bar = self.fill_char * bar_length
- bar += self.empty_char * (self.width - bar_length)
- elif self.finished:
- bar = self.fill_char * self.width
- else:
- chars = list(self.empty_char * (self.width or 1))
- if self.time_per_iteration != 0:
- chars[
- int(
- (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
- * self.width
- )
- ] = self.fill_char
- bar = "".join(chars)
- return bar
-
- def format_progress_line(self) -> str:
- show_percent = self.show_percent
-
- info_bits = []
- if self.length is not None and show_percent is None:
- show_percent = not self.show_pos
-
- if self.show_pos:
- info_bits.append(self.format_pos())
- if show_percent:
- info_bits.append(self.format_pct())
- if self.show_eta and self.eta_known and not self.finished:
- info_bits.append(self.format_eta())
- if self.item_show_func is not None:
- item_info = self.item_show_func(self.current_item)
- if item_info is not None:
- info_bits.append(item_info)
-
- return (
- self.bar_template
- % {
- "label": self.label,
- "bar": self.format_bar(),
- "info": self.info_sep.join(info_bits),
- }
- ).rstrip()
-
- def render_progress(self) -> None:
- if self.hidden:
- return
-
- if not self._is_atty:
- # Only output the label once if the output is not a TTY.
- if self._last_line != self.label:
- self._last_line = self.label
- echo(self.label, file=self.file, color=self.color)
- return
-
- buf = []
- # Update width in case the terminal has been resized
- if self.autowidth:
- import shutil
-
- old_width = self.width
- self.width = 0
- clutter_length = term_len(self.format_progress_line())
- new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
- if new_width < old_width and self.max_width is not None:
- buf.append(BEFORE_BAR)
- buf.append(" " * self.max_width)
- self.max_width = new_width
- self.width = new_width
-
- clear_width = self.width
- if self.max_width is not None:
- clear_width = self.max_width
-
- buf.append(BEFORE_BAR)
- line = self.format_progress_line()
- line_len = term_len(line)
- if self.max_width is None or self.max_width < line_len:
- self.max_width = line_len
-
- buf.append(line)
- buf.append(" " * (clear_width - line_len))
- line = "".join(buf)
- # Render the line only if it changed.
-
- if line != self._last_line:
- self._last_line = line
- echo(line, file=self.file, color=self.color, nl=False)
- self.file.flush()
-
- def make_step(self, n_steps: int) -> None:
- self.pos += n_steps
- if self.length is not None and self.pos >= self.length:
- self.finished = True
-
- if (time.time() - self.last_eta) < 1.0:
- return
-
- self.last_eta = time.time()
-
- # self.avg is a rolling list of length <= 7 of steps where steps are
- # defined as time elapsed divided by the total progress through
- # self.length.
- if self.pos:
- step = (time.time() - self.start) / self.pos
- else:
- step = time.time() - self.start
-
- self.avg = self.avg[-6:] + [step]
-
- self.eta_known = self.length is not None
-
- def update(self, n_steps: int, current_item: V | None = None) -> None:
- """Update the progress bar by advancing a specified number of
- steps, and optionally set the ``current_item`` for this new
- position.
-
- :param n_steps: Number of steps to advance.
- :param current_item: Optional item to set as ``current_item``
- for the updated position.
-
- .. versionchanged:: 8.0
- Added the ``current_item`` optional parameter.
-
- .. versionchanged:: 8.0
- Only render when the number of steps meets the
- ``update_min_steps`` threshold.
- """
- if current_item is not None:
- self.current_item = current_item
-
- self._completed_intervals += n_steps
-
- if self._completed_intervals >= self.update_min_steps:
- self.make_step(self._completed_intervals)
- self.render_progress()
- self._completed_intervals = 0
-
- def finish(self) -> None:
- self.eta_known = False
- self.current_item = None
- self.finished = True
-
- def generator(self) -> cabc.Iterator[V]:
- """Return a generator which yields the items added to the bar
- during construction, and updates the progress bar *after* the
- yielded block returns.
- """
- # WARNING: the iterator interface for `ProgressBar` relies on
- # this and only works because this is a simple generator which
- # doesn't create or manage additional state. If this function
- # changes, the impact should be evaluated both against
- # `iter(bar)` and `next(bar)`. `next()` in particular may call
- # `self.generator()` repeatedly, and this must remain safe in
- # order for that interface to work.
- if not self.entered:
- raise RuntimeError("You need to use progress bars in a with block.")
-
- if not self._is_atty:
- yield from self.iter
- else:
- for rv in self.iter:
- self.current_item = rv
-
- # This allows show_item_func to be updated before the
- # item is processed. Only trigger at the beginning of
- # the update interval.
- if self._completed_intervals == 0:
- self.render_progress()
-
- yield rv
- self.update(1)
-
- self.finish()
- self.render_progress()
-
-
-def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None:
- """Decide what method to use for paging through text."""
- stdout = _default_text_stdout()
-
- # There are no standard streams attached to write to. For example,
- # pythonw on Windows.
- if stdout is None:
- stdout = StringIO()
-
- if not isatty(sys.stdin) or not isatty(stdout):
- return _nullpager(stdout, generator, color)
-
- # Split and normalize the pager command into parts.
- pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False)
- if pager_cmd_parts:
- if WIN:
- if _tempfilepager(generator, pager_cmd_parts, color):
- return
- elif _pipepager(generator, pager_cmd_parts, color):
- return
-
- if os.environ.get("TERM") in ("dumb", "emacs"):
- return _nullpager(stdout, generator, color)
- if (WIN or sys.platform.startswith("os2")) and _tempfilepager(
- generator, ["more"], color
- ):
- return
- if _pipepager(generator, ["less"], color):
- return
-
- import tempfile
-
- fd, filename = tempfile.mkstemp()
- os.close(fd)
- try:
- if _pipepager(generator, ["more"], color):
- return
- return _nullpager(stdout, generator, color)
- finally:
- os.unlink(filename)
-
-
-def _pipepager(
- generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
-) -> bool:
- """Page through text by feeding it to another program. Invoking a
- pager through this might support colors.
-
- Returns `True` if the command was found, `False` otherwise and thus another
- pager should be attempted.
- """
- # Split the command into the invoked CLI and its parameters.
- if not cmd_parts:
- return False
-
- import shutil
-
- cmd = cmd_parts[0]
- cmd_params = cmd_parts[1:]
-
- cmd_filepath = shutil.which(cmd)
- if not cmd_filepath:
- return False
-
- # Produces a normalized absolute path string.
- # multi-call binaries such as busybox derive their identity from the symlink
- # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
- cmd_path = Path(cmd_filepath).absolute()
- cmd_name = cmd_path.name
-
- import subprocess
-
- # Make a local copy of the environment to not affect the global one.
- env = dict(os.environ)
-
- # If we're piping to less and the user hasn't decided on colors, we enable
- # them by default we find the -R flag in the command line arguments.
- if color is None and cmd_name == "less":
- less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}"
- if not less_flags:
- env["LESS"] = "-R"
- color = True
- elif "r" in less_flags or "R" in less_flags:
- color = True
-
- c = subprocess.Popen(
- [str(cmd_path)] + cmd_params,
- shell=False,
- stdin=subprocess.PIPE,
- env=env,
- errors="replace",
- text=True,
- )
- assert c.stdin is not None
- try:
- for text in generator:
- if not color:
- text = strip_ansi(text)
-
- c.stdin.write(text)
- except BrokenPipeError:
- # In case the pager exited unexpectedly, ignore the broken pipe error.
- pass
- except Exception as e:
- # In case there is an exception we want to close the pager immediately
- # and let the caller handle it.
- # Otherwise the pager will keep running, and the user may not notice
- # the error message, or worse yet it may leave the terminal in a broken state.
- c.terminate()
- raise e
- finally:
- # We must close stdin and wait for the pager to exit before we continue
- try:
- c.stdin.close()
- # Close implies flush, so it might throw a BrokenPipeError if the pager
- # process exited already.
- except BrokenPipeError:
- pass
-
- # Less doesn't respect ^C, but catches it for its own UI purposes (aborting
- # search or other commands inside less).
- #
- # That means when the user hits ^C, the parent process (click) terminates,
- # but less is still alive, paging the output and messing up the terminal.
- #
- # If the user wants to make the pager exit on ^C, they should set
- # `LESS='-K'`. It's not our decision to make.
- while True:
- try:
- c.wait()
- except KeyboardInterrupt:
- pass
- else:
- break
-
- return True
-
-
-def _tempfilepager(
- generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
-) -> bool:
- """Page through text by invoking a program on a temporary file.
-
- Returns `True` if the command was found, `False` otherwise and thus another
- pager should be attempted.
- """
- # Split the command into the invoked CLI and its parameters.
- if not cmd_parts:
- return False
-
- import shutil
-
- cmd = cmd_parts[0]
-
- cmd_filepath = shutil.which(cmd)
- if not cmd_filepath:
- return False
- # Produces a normalized absolute path string.
- # multi-call binaries such as busybox derive their identity from the symlink
- # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
- cmd_path = Path(cmd_filepath).absolute()
-
- import subprocess
- import tempfile
-
- fd, filename = tempfile.mkstemp()
- # TODO: This never terminates if the passed generator never terminates.
- text = "".join(generator)
- if not color:
- text = strip_ansi(text)
- encoding = get_best_encoding(sys.stdout)
- with open_stream(filename, "wb")[0] as f:
- f.write(text.encode(encoding))
- try:
- subprocess.call([str(cmd_path), filename])
- except OSError:
- # Command not found
- pass
- finally:
- os.close(fd)
- os.unlink(filename)
-
- return True
-
-
-def _nullpager(
- stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None
-) -> None:
- """Simply print unformatted text. This is the ultimate fallback."""
- for text in generator:
- if not color:
- text = strip_ansi(text)
- stream.write(text)
-
-
-class Editor:
- def __init__(
- self,
- editor: str | None = None,
- env: cabc.Mapping[str, str] | None = None,
- require_save: bool = True,
- extension: str = ".txt",
- ) -> None:
- self.editor = editor
- self.env = env
- self.require_save = require_save
- self.extension = extension
-
- def get_editor(self) -> str:
- if self.editor is not None:
- return self.editor
- for key in "VISUAL", "EDITOR":
- rv = os.environ.get(key)
- if rv:
- return rv
- if WIN:
- return "notepad"
-
- from shutil import which
-
- for editor in "sensible-editor", "vim", "nano":
- if which(editor) is not None:
- return editor
- return "vi"
-
- def edit_files(self, filenames: cabc.Iterable[str]) -> None:
- import subprocess
-
- editor = self.get_editor()
- environ: dict[str, str] | None = None
-
- if self.env:
- environ = os.environ.copy()
- environ.update(self.env)
-
- exc_filename = " ".join(f'"{filename}"' for filename in filenames)
-
- try:
- c = subprocess.Popen(
- args=f"{editor} {exc_filename}", env=environ, shell=True
- )
- exit_code = c.wait()
- if exit_code != 0:
- raise ClickException(
- _("{editor}: Editing failed").format(editor=editor)
- )
- except OSError as e:
- raise ClickException(
- _("{editor}: Editing failed: {e}").format(editor=editor, e=e)
- ) from e
-
- @t.overload
- def edit(self, text: bytes | bytearray) -> bytes | None: ...
-
- # We cannot know whether or not the type expected is str or bytes when None
- # is passed, so str is returned as that was what was done before.
- @t.overload
- def edit(self, text: str | None) -> str | None: ...
-
- def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None:
- import tempfile
-
- if text is None:
- data: bytes | bytearray = b""
- elif isinstance(text, (bytes, bytearray)):
- data = text
- else:
- if text and not text.endswith("\n"):
- text += "\n"
-
- if WIN:
- data = text.replace("\n", "\r\n").encode("utf-8-sig")
- else:
- data = text.encode("utf-8")
-
- fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
- f: t.BinaryIO
-
- try:
- with os.fdopen(fd, "wb") as f:
- f.write(data)
-
- # If the filesystem resolution is 1 second, like Mac OS
- # 10.12 Extended, or 2 seconds, like FAT32, and the editor
- # closes very fast, require_save can fail. Set the modified
- # time to be 2 seconds in the past to work around this.
- os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
- # Depending on the resolution, the exact value might not be
- # recorded, so get the new recorded value.
- timestamp = os.path.getmtime(name)
-
- self.edit_files((name,))
-
- if self.require_save and os.path.getmtime(name) == timestamp:
- return None
-
- with open(name, "rb") as f:
- rv = f.read()
-
- if isinstance(text, (bytes, bytearray)):
- return rv
-
- return rv.decode("utf-8-sig").replace("\r\n", "\n")
- finally:
- os.unlink(name)
-
-
-def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
- import subprocess
-
- def _unquote_file(url: str) -> str:
- from urllib.parse import unquote
-
- if url.startswith("file://"):
- url = unquote(url[7:])
-
- return url
-
- if sys.platform == "darwin":
- args = ["open"]
- if wait:
- args.append("-W")
- if locate:
- args.append("-R")
- args.append(_unquote_file(url))
- null = open("/dev/null", "w")
- try:
- return subprocess.Popen(args, stderr=null).wait()
- finally:
- null.close()
- elif WIN:
- if locate:
- url = _unquote_file(url)
- args = ["explorer", f"/select,{url}"]
- else:
- args = ["start"]
- if wait:
- args.append("/WAIT")
- args.append("")
- args.append(url)
- try:
- return subprocess.call(args)
- except OSError:
- # Command not found
- return 127
- elif CYGWIN:
- if locate:
- url = _unquote_file(url)
- args = ["cygstart", os.path.dirname(url)]
- else:
- args = ["cygstart"]
- if wait:
- args.append("-w")
- args.append(url)
- try:
- return subprocess.call(args)
- except OSError:
- # Command not found
- return 127
-
- try:
- if locate:
- url = os.path.dirname(_unquote_file(url)) or "."
- else:
- url = _unquote_file(url)
- c = subprocess.Popen(["xdg-open", url])
- if wait:
- return c.wait()
- return 0
- except OSError:
- if url.startswith(("http://", "https://")) and not locate and not wait:
- import webbrowser
-
- webbrowser.open(url)
- return 0
- return 1
-
-
-def _translate_ch_to_exc(ch: str) -> None:
- if ch == "\x03":
- raise KeyboardInterrupt()
-
- if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
- raise EOFError()
-
- if ch == "\x1a" and WIN: # Windows, Ctrl+Z
- raise EOFError()
-
- return None
-
-
-if sys.platform == "win32":
- import msvcrt
-
- @contextlib.contextmanager
- def raw_terminal() -> cabc.Iterator[int]:
- yield -1
-
- def getchar(echo: bool) -> str:
- # The function `getch` will return a bytes object corresponding to
- # the pressed character. Since Windows 10 build 1803, it will also
- # return \x00 when called a second time after pressing a regular key.
- #
- # `getwch` does not share this probably-bugged behavior. Moreover, it
- # returns a Unicode object by default, which is what we want.
- #
- # Either of these functions will return \x00 or \xe0 to indicate
- # a special key, and you need to call the same function again to get
- # the "rest" of the code. The fun part is that \u00e0 is
- # "latin small letter a with grave", so if you type that on a French
- # keyboard, you _also_ get a \xe0.
- # E.g., consider the Up arrow. This returns \xe0 and then \x48. The
- # resulting Unicode string reads as "a with grave" + "capital H".
- # This is indistinguishable from when the user actually types
- # "a with grave" and then "capital H".
- #
- # When \xe0 is returned, we assume it's part of a special-key sequence
- # and call `getwch` again, but that means that when the user types
- # the \u00e0 character, `getchar` doesn't return until a second
- # character is typed.
- # The alternative is returning immediately, but that would mess up
- # cross-platform handling of arrow keys and others that start with
- # \xe0. Another option is using `getch`, but then we can't reliably
- # read non-ASCII characters, because return values of `getch` are
- # limited to the current 8-bit codepage.
- #
- # Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
- # is doing the right thing in more situations than with `getch`.
-
- if echo:
- func = t.cast(t.Callable[[], str], msvcrt.getwche)
- else:
- func = t.cast(t.Callable[[], str], msvcrt.getwch)
-
- rv = func()
-
- if rv in ("\x00", "\xe0"):
- # \x00 and \xe0 are control characters that indicate special key,
- # see above.
- rv += func()
-
- _translate_ch_to_exc(rv)
- return rv
-
-else:
- import termios
- import tty
-
- @contextlib.contextmanager
- def raw_terminal() -> cabc.Iterator[int]:
- f: t.TextIO | None
- fd: int
-
- if not isatty(sys.stdin):
- f = open("/dev/tty")
- fd = f.fileno()
- else:
- fd = sys.stdin.fileno()
- f = None
-
- try:
- old_settings = termios.tcgetattr(fd)
-
- try:
- tty.setraw(fd)
- yield fd
- finally:
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
- sys.stdout.flush()
-
- if f is not None:
- f.close()
- except termios.error:
- pass
-
- def getchar(echo: bool) -> str:
- with raw_terminal() as fd:
- ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
-
- if echo and isatty(sys.stdout):
- sys.stdout.write(ch)
-
- _translate_ch_to_exc(ch)
- return ch
diff --git a/backend/.venv/lib/python3.12/site-packages/click/_textwrap.py b/backend/.venv/lib/python3.12/site-packages/click/_textwrap.py
deleted file mode 100644
index 97fbee3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/_textwrap.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import textwrap
-from contextlib import contextmanager
-
-
-class TextWrapper(textwrap.TextWrapper):
- def _handle_long_word(
- self,
- reversed_chunks: list[str],
- cur_line: list[str],
- cur_len: int,
- width: int,
- ) -> None:
- space_left = max(width - cur_len, 1)
-
- if self.break_long_words:
- last = reversed_chunks[-1]
- cut = last[:space_left]
- res = last[space_left:]
- cur_line.append(cut)
- reversed_chunks[-1] = res
- elif not cur_line:
- cur_line.append(reversed_chunks.pop())
-
- @contextmanager
- def extra_indent(self, indent: str) -> cabc.Iterator[None]:
- old_initial_indent = self.initial_indent
- old_subsequent_indent = self.subsequent_indent
- self.initial_indent += indent
- self.subsequent_indent += indent
-
- try:
- yield
- finally:
- self.initial_indent = old_initial_indent
- self.subsequent_indent = old_subsequent_indent
-
- def indent_only(self, text: str) -> str:
- rv = []
-
- for idx, line in enumerate(text.splitlines()):
- indent = self.initial_indent
-
- if idx > 0:
- indent = self.subsequent_indent
-
- rv.append(f"{indent}{line}")
-
- return "\n".join(rv)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/_utils.py b/backend/.venv/lib/python3.12/site-packages/click/_utils.py
deleted file mode 100644
index 09fb008..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/_utils.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from __future__ import annotations
-
-import enum
-import typing as t
-
-
-class Sentinel(enum.Enum):
- """Enum used to define sentinel values.
-
- .. seealso::
-
- `PEP 661 - Sentinel Values `_.
- """
-
- UNSET = object()
- FLAG_NEEDS_VALUE = object()
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}.{self.name}"
-
-
-UNSET = Sentinel.UNSET
-"""Sentinel used to indicate that a value is not set."""
-
-FLAG_NEEDS_VALUE = Sentinel.FLAG_NEEDS_VALUE
-"""Sentinel used to indicate an option was passed as a flag without a
-value but is not a flag option.
-
-``Option.consume_value`` uses this to prompt or use the ``flag_value``.
-"""
-
-T_UNSET = t.Literal[UNSET] # type: ignore[valid-type]
-"""Type hint for the :data:`UNSET` sentinel value."""
-
-T_FLAG_NEEDS_VALUE = t.Literal[FLAG_NEEDS_VALUE] # type: ignore[valid-type]
-"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value."""
diff --git a/backend/.venv/lib/python3.12/site-packages/click/_winconsole.py b/backend/.venv/lib/python3.12/site-packages/click/_winconsole.py
deleted file mode 100644
index e56c7c6..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/_winconsole.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# This module is based on the excellent work by Adam Bartoš who
-# provided a lot of what went into the implementation here in
-# the discussion to issue1602 in the Python bug tracker.
-#
-# There are some general differences in regards to how this works
-# compared to the original patches as we do not need to patch
-# the entire interpreter but just work in our little world of
-# echo and prompt.
-from __future__ import annotations
-
-import collections.abc as cabc
-import io
-import sys
-import time
-import typing as t
-from ctypes import Array
-from ctypes import byref
-from ctypes import c_char
-from ctypes import c_char_p
-from ctypes import c_int
-from ctypes import c_ssize_t
-from ctypes import c_ulong
-from ctypes import c_void_p
-from ctypes import POINTER
-from ctypes import py_object
-from ctypes import Structure
-from ctypes.wintypes import DWORD
-from ctypes.wintypes import HANDLE
-from ctypes.wintypes import LPCWSTR
-from ctypes.wintypes import LPWSTR
-
-from ._compat import _NonClosingTextIOWrapper
-
-assert sys.platform == "win32"
-import msvcrt # noqa: E402
-from ctypes import windll # noqa: E402
-from ctypes import WINFUNCTYPE # noqa: E402
-
-c_ssize_p = POINTER(c_ssize_t)
-
-kernel32 = windll.kernel32
-GetStdHandle = kernel32.GetStdHandle
-ReadConsoleW = kernel32.ReadConsoleW
-WriteConsoleW = kernel32.WriteConsoleW
-GetConsoleMode = kernel32.GetConsoleMode
-GetLastError = kernel32.GetLastError
-GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
-CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
- ("CommandLineToArgvW", windll.shell32)
-)
-LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
-
-STDIN_HANDLE = GetStdHandle(-10)
-STDOUT_HANDLE = GetStdHandle(-11)
-STDERR_HANDLE = GetStdHandle(-12)
-
-PyBUF_SIMPLE = 0
-PyBUF_WRITABLE = 1
-
-ERROR_SUCCESS = 0
-ERROR_NOT_ENOUGH_MEMORY = 8
-ERROR_OPERATION_ABORTED = 995
-
-STDIN_FILENO = 0
-STDOUT_FILENO = 1
-STDERR_FILENO = 2
-
-EOF = b"\x1a"
-MAX_BYTES_WRITTEN = 32767
-
-if t.TYPE_CHECKING:
- try:
- # Using `typing_extensions.Buffer` instead of `collections.abc`
- # on Windows for some reason does not have `Sized` implemented.
- from collections.abc import Buffer # type: ignore
- except ImportError:
- from typing_extensions import Buffer
-
-try:
- from ctypes import pythonapi
-except ImportError:
- # On PyPy we cannot get buffers so our ability to operate here is
- # severely limited.
- get_buffer = None
-else:
-
- class Py_buffer(Structure):
- _fields_ = [ # noqa: RUF012
- ("buf", c_void_p),
- ("obj", py_object),
- ("len", c_ssize_t),
- ("itemsize", c_ssize_t),
- ("readonly", c_int),
- ("ndim", c_int),
- ("format", c_char_p),
- ("shape", c_ssize_p),
- ("strides", c_ssize_p),
- ("suboffsets", c_ssize_p),
- ("internal", c_void_p),
- ]
-
- PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
- PyBuffer_Release = pythonapi.PyBuffer_Release
-
- def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]:
- buf = Py_buffer()
- flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
- PyObject_GetBuffer(py_object(obj), byref(buf), flags)
-
- try:
- buffer_type = c_char * buf.len
- out: Array[c_char] = buffer_type.from_address(buf.buf)
- return out
- finally:
- PyBuffer_Release(byref(buf))
-
-
-class _WindowsConsoleRawIOBase(io.RawIOBase):
- def __init__(self, handle: int | None) -> None:
- self.handle = handle
-
- def isatty(self) -> t.Literal[True]:
- super().isatty()
- return True
-
-
-class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
- def readable(self) -> t.Literal[True]:
- return True
-
- def readinto(self, b: Buffer) -> int:
- bytes_to_be_read = len(b)
- if not bytes_to_be_read:
- return 0
- elif bytes_to_be_read % 2:
- raise ValueError(
- "cannot read odd number of bytes from UTF-16-LE encoded console"
- )
-
- buffer = get_buffer(b, writable=True)
- code_units_to_be_read = bytes_to_be_read // 2
- code_units_read = c_ulong()
-
- rv = ReadConsoleW(
- HANDLE(self.handle),
- buffer,
- code_units_to_be_read,
- byref(code_units_read),
- None,
- )
- if GetLastError() == ERROR_OPERATION_ABORTED:
- # wait for KeyboardInterrupt
- time.sleep(0.1)
- if not rv:
- raise OSError(f"Windows error: {GetLastError()}")
-
- if buffer[0] == EOF:
- return 0
- return 2 * code_units_read.value
-
-
-class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
- def writable(self) -> t.Literal[True]:
- return True
-
- @staticmethod
- def _get_error_message(errno: int) -> str:
- if errno == ERROR_SUCCESS:
- return "ERROR_SUCCESS"
- elif errno == ERROR_NOT_ENOUGH_MEMORY:
- return "ERROR_NOT_ENOUGH_MEMORY"
- return f"Windows error {errno}"
-
- def write(self, b: Buffer) -> int:
- bytes_to_be_written = len(b)
- buf = get_buffer(b)
- code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
- code_units_written = c_ulong()
-
- WriteConsoleW(
- HANDLE(self.handle),
- buf,
- code_units_to_be_written,
- byref(code_units_written),
- None,
- )
- bytes_written = 2 * code_units_written.value
-
- if bytes_written == 0 and bytes_to_be_written > 0:
- raise OSError(self._get_error_message(GetLastError()))
- return bytes_written
-
-
-class ConsoleStream:
- def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
- self._text_stream = text_stream
- self.buffer = byte_stream
-
- @property
- def name(self) -> str:
- return self.buffer.name
-
- def write(self, x: t.AnyStr) -> int:
- if isinstance(x, str):
- return self._text_stream.write(x)
- try:
- self.flush()
- except Exception:
- pass
- return self.buffer.write(x)
-
- def writelines(self, lines: cabc.Iterable[t.AnyStr]) -> None:
- for line in lines:
- self.write(line)
-
- def __getattr__(self, name: str) -> t.Any:
- return getattr(self._text_stream, name)
-
- def isatty(self) -> bool:
- return self.buffer.isatty()
-
- def __repr__(self) -> str:
- return f""
-
-
-def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
- text_stream = _NonClosingTextIOWrapper(
- io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
- "utf-16-le",
- "strict",
- line_buffering=True,
- )
- return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
-
-
-def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
- text_stream = _NonClosingTextIOWrapper(
- io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
- "utf-16-le",
- "strict",
- line_buffering=True,
- )
- return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
-
-
-def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
- text_stream = _NonClosingTextIOWrapper(
- io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
- "utf-16-le",
- "strict",
- line_buffering=True,
- )
- return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
-
-
-_stream_factories: cabc.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
- 0: _get_text_stdin,
- 1: _get_text_stdout,
- 2: _get_text_stderr,
-}
-
-
-def _is_console(f: t.TextIO) -> bool:
- if not hasattr(f, "fileno"):
- return False
-
- try:
- fileno = f.fileno()
- except (OSError, io.UnsupportedOperation):
- return False
-
- handle = msvcrt.get_osfhandle(fileno)
- return bool(GetConsoleMode(handle, byref(DWORD())))
-
-
-def _get_windows_console_stream(
- f: t.TextIO, encoding: str | None, errors: str | None
-) -> t.TextIO | None:
- if (
- get_buffer is None
- or encoding not in {"utf-16-le", None}
- or errors not in {"strict", None}
- or not _is_console(f)
- ):
- return None
-
- func = _stream_factories.get(f.fileno())
- if func is None:
- return None
-
- b = getattr(f, "buffer", None)
-
- if b is None:
- return None
-
- return func(b)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/core.py b/backend/.venv/lib/python3.12/site-packages/click/core.py
deleted file mode 100644
index 57f549c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/core.py
+++ /dev/null
@@ -1,3415 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import enum
-import errno
-import inspect
-import os
-import sys
-import typing as t
-from collections import abc
-from collections import Counter
-from contextlib import AbstractContextManager
-from contextlib import contextmanager
-from contextlib import ExitStack
-from functools import update_wrapper
-from gettext import gettext as _
-from gettext import ngettext
-from itertools import repeat
-from types import TracebackType
-
-from . import types
-from ._utils import FLAG_NEEDS_VALUE
-from ._utils import UNSET
-from .exceptions import Abort
-from .exceptions import BadParameter
-from .exceptions import ClickException
-from .exceptions import Exit
-from .exceptions import MissingParameter
-from .exceptions import NoArgsIsHelpError
-from .exceptions import UsageError
-from .formatting import HelpFormatter
-from .formatting import join_options
-from .globals import pop_context
-from .globals import push_context
-from .parser import _OptionParser
-from .parser import _split_opt
-from .termui import confirm
-from .termui import prompt
-from .termui import style
-from .utils import _detect_program_name
-from .utils import _expand_args
-from .utils import echo
-from .utils import make_default_short_help
-from .utils import make_str
-from .utils import PacifyFlushWrapper
-
-if t.TYPE_CHECKING:
- from .shell_completion import CompletionItem
-
-F = t.TypeVar("F", bound="t.Callable[..., t.Any]")
-V = t.TypeVar("V")
-
-
-def _complete_visible_commands(
- ctx: Context, incomplete: str
-) -> cabc.Iterator[tuple[str, Command]]:
- """List all the subcommands of a group that start with the
- incomplete value and aren't hidden.
-
- :param ctx: Invocation context for the group.
- :param incomplete: Value being completed. May be empty.
- """
- multi = t.cast(Group, ctx.command)
-
- for name in multi.list_commands(ctx):
- if name.startswith(incomplete):
- command = multi.get_command(ctx, name)
-
- if command is not None and not command.hidden:
- yield name, command
-
-
-def _check_nested_chain(
- base_command: Group, cmd_name: str, cmd: Command, register: bool = False
-) -> None:
- if not base_command.chain or not isinstance(cmd, Group):
- return
-
- if register:
- message = (
- f"It is not possible to add the group {cmd_name!r} to another"
- f" group {base_command.name!r} that is in chain mode."
- )
- else:
- message = (
- f"Found the group {cmd_name!r} as subcommand to another group "
- f" {base_command.name!r} that is in chain mode. This is not supported."
- )
-
- raise RuntimeError(message)
-
-
-def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]:
- return list(zip(*repeat(iter(iterable), batch_size), strict=False))
-
-
-@contextmanager
-def augment_usage_errors(
- ctx: Context, param: Parameter | None = None
-) -> cabc.Iterator[None]:
- """Context manager that attaches extra information to exceptions."""
- try:
- yield
- except BadParameter as e:
- if e.ctx is None:
- e.ctx = ctx
- if param is not None and e.param is None:
- e.param = param
- raise
- except UsageError as e:
- if e.ctx is None:
- e.ctx = ctx
- raise
-
-
-def iter_params_for_processing(
- invocation_order: cabc.Sequence[Parameter],
- declaration_order: cabc.Sequence[Parameter],
-) -> list[Parameter]:
- """Returns all declared parameters in the order they should be processed.
-
- The declared parameters are re-shuffled depending on the order in which
- they were invoked, as well as the eagerness of each parameters.
-
- The invocation order takes precedence over the declaration order. I.e. the
- order in which the user provided them to the CLI is respected.
-
- This behavior and its effect on callback evaluation is detailed at:
- https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order
- """
-
- def sort_key(item: Parameter) -> tuple[bool, float]:
- try:
- idx: float = invocation_order.index(item)
- except ValueError:
- idx = float("inf")
-
- return not item.is_eager, idx
-
- return sorted(declaration_order, key=sort_key)
-
-
-class ParameterSource(enum.Enum):
- """This is an :class:`~enum.Enum` that indicates the source of a
- parameter's value.
-
- Use :meth:`click.Context.get_parameter_source` to get the
- source for a parameter by name.
-
- .. versionchanged:: 8.0
- Use :class:`~enum.Enum` and drop the ``validate`` method.
-
- .. versionchanged:: 8.0
- Added the ``PROMPT`` value.
- """
-
- COMMANDLINE = enum.auto()
- """The value was provided by the command line args."""
- ENVIRONMENT = enum.auto()
- """The value was provided with an environment variable."""
- DEFAULT = enum.auto()
- """Used the default specified by the parameter."""
- DEFAULT_MAP = enum.auto()
- """Used a default provided by :attr:`Context.default_map`."""
- PROMPT = enum.auto()
- """Used a prompt to confirm a default or provide a value."""
-
-
-class Context:
- """The context is a special internal object that holds state relevant
- for the script execution at every single level. It's normally invisible
- to commands unless they opt-in to getting access to it.
-
- The context is useful as it can pass internal objects around and can
- control special execution features such as reading data from
- environment variables.
-
- A context can be used as context manager in which case it will call
- :meth:`close` on teardown.
-
- :param command: the command class for this context.
- :param parent: the parent context.
- :param info_name: the info name for this invocation. Generally this
- is the most descriptive name for the script or
- command. For the toplevel script it is usually
- the name of the script, for commands below it it's
- the name of the script.
- :param obj: an arbitrary object of user data.
- :param auto_envvar_prefix: the prefix to use for automatic environment
- variables. If this is `None` then reading
- from environment variables is disabled. This
- does not affect manually set environment
- variables which are always read.
- :param default_map: a dictionary (like object) with default values
- for parameters.
- :param terminal_width: the width of the terminal. The default is
- inherit from parent context. If no context
- defines the terminal width then auto
- detection will be applied.
- :param max_content_width: the maximum width for content rendered by
- Click (this currently only affects help
- pages). This defaults to 80 characters if
- not overridden. In other words: even if the
- terminal is larger than that, Click will not
- format things wider than 80 characters by
- default. In addition to that, formatters might
- add some safety mapping on the right.
- :param resilient_parsing: if this flag is enabled then Click will
- parse without any interactivity or callback
- invocation. Default values will also be
- ignored. This is useful for implementing
- things such as completion support.
- :param allow_extra_args: if this is set to `True` then extra arguments
- at the end will not raise an error and will be
- kept on the context. The default is to inherit
- from the command.
- :param allow_interspersed_args: if this is set to `False` then options
- and arguments cannot be mixed. The
- default is to inherit from the command.
- :param ignore_unknown_options: instructs click to ignore options it does
- not know and keeps them for later
- processing.
- :param help_option_names: optionally a list of strings that define how
- the default help parameter is named. The
- default is ``['--help']``.
- :param token_normalize_func: an optional function that is used to
- normalize tokens (options, choices,
- etc.). This for instance can be used to
- implement case insensitive behavior.
- :param color: controls if the terminal supports ANSI colors or not. The
- default is autodetection. This is only needed if ANSI
- codes are used in texts that Click prints which is by
- default not the case. This for instance would affect
- help output.
- :param show_default: Show the default value for commands. If this
- value is not set, it defaults to the value from the parent
- context. ``Command.show_default`` overrides this default for the
- specific command.
-
- .. versionchanged:: 8.2
- The ``protected_args`` attribute is deprecated and will be removed in
- Click 9.0. ``args`` will contain remaining unparsed tokens.
-
- .. versionchanged:: 8.1
- The ``show_default`` parameter is overridden by
- ``Command.show_default``, instead of the other way around.
-
- .. versionchanged:: 8.0
- The ``show_default`` parameter defaults to the value from the
- parent context.
-
- .. versionchanged:: 7.1
- Added the ``show_default`` parameter.
-
- .. versionchanged:: 4.0
- Added the ``color``, ``ignore_unknown_options``, and
- ``max_content_width`` parameters.
-
- .. versionchanged:: 3.0
- Added the ``allow_extra_args`` and ``allow_interspersed_args``
- parameters.
-
- .. versionchanged:: 2.0
- Added the ``resilient_parsing``, ``help_option_names``, and
- ``token_normalize_func`` parameters.
- """
-
- #: The formatter class to create with :meth:`make_formatter`.
- #:
- #: .. versionadded:: 8.0
- formatter_class: type[HelpFormatter] = HelpFormatter
-
- def __init__(
- self,
- command: Command,
- parent: Context | None = None,
- info_name: str | None = None,
- obj: t.Any | None = None,
- auto_envvar_prefix: str | None = None,
- default_map: cabc.MutableMapping[str, t.Any] | None = None,
- terminal_width: int | None = None,
- max_content_width: int | None = None,
- resilient_parsing: bool = False,
- allow_extra_args: bool | None = None,
- allow_interspersed_args: bool | None = None,
- ignore_unknown_options: bool | None = None,
- help_option_names: list[str] | None = None,
- token_normalize_func: t.Callable[[str], str] | None = None,
- color: bool | None = None,
- show_default: bool | None = None,
- ) -> None:
- #: the parent context or `None` if none exists.
- self.parent = parent
- #: the :class:`Command` for this context.
- self.command = command
- #: the descriptive information name
- self.info_name = info_name
- #: Map of parameter names to their parsed values. Parameters
- #: with ``expose_value=False`` are not stored.
- self.params: dict[str, t.Any] = {}
- #: the leftover arguments.
- self.args: list[str] = []
- #: protected arguments. These are arguments that are prepended
- #: to `args` when certain parsing scenarios are encountered but
- #: must be never propagated to another arguments. This is used
- #: to implement nested parsing.
- self._protected_args: list[str] = []
- #: the collected prefixes of the command's options.
- self._opt_prefixes: set[str] = set(parent._opt_prefixes) if parent else set()
-
- if obj is None and parent is not None:
- obj = parent.obj
-
- #: the user object stored.
- self.obj: t.Any = obj
- self._meta: dict[str, t.Any] = getattr(parent, "meta", {})
-
- #: A dictionary (-like object) with defaults for parameters.
- if (
- default_map is None
- and info_name is not None
- and parent is not None
- and parent.default_map is not None
- ):
- default_map = parent.default_map.get(info_name)
-
- self.default_map: cabc.MutableMapping[str, t.Any] | None = default_map
-
- #: This flag indicates if a subcommand is going to be executed. A
- #: group callback can use this information to figure out if it's
- #: being executed directly or because the execution flow passes
- #: onwards to a subcommand. By default it's None, but it can be
- #: the name of the subcommand to execute.
- #:
- #: If chaining is enabled this will be set to ``'*'`` in case
- #: any commands are executed. It is however not possible to
- #: figure out which ones. If you require this knowledge you
- #: should use a :func:`result_callback`.
- self.invoked_subcommand: str | None = None
-
- if terminal_width is None and parent is not None:
- terminal_width = parent.terminal_width
-
- #: The width of the terminal (None is autodetection).
- self.terminal_width: int | None = terminal_width
-
- if max_content_width is None and parent is not None:
- max_content_width = parent.max_content_width
-
- #: The maximum width of formatted content (None implies a sensible
- #: default which is 80 for most things).
- self.max_content_width: int | None = max_content_width
-
- if allow_extra_args is None:
- allow_extra_args = command.allow_extra_args
-
- #: Indicates if the context allows extra args or if it should
- #: fail on parsing.
- #:
- #: .. versionadded:: 3.0
- self.allow_extra_args = allow_extra_args
-
- if allow_interspersed_args is None:
- allow_interspersed_args = command.allow_interspersed_args
-
- #: Indicates if the context allows mixing of arguments and
- #: options or not.
- #:
- #: .. versionadded:: 3.0
- self.allow_interspersed_args: bool = allow_interspersed_args
-
- if ignore_unknown_options is None:
- ignore_unknown_options = command.ignore_unknown_options
-
- #: Instructs click to ignore options that a command does not
- #: understand and will store it on the context for later
- #: processing. This is primarily useful for situations where you
- #: want to call into external programs. Generally this pattern is
- #: strongly discouraged because it's not possibly to losslessly
- #: forward all arguments.
- #:
- #: .. versionadded:: 4.0
- self.ignore_unknown_options: bool = ignore_unknown_options
-
- if help_option_names is None:
- if parent is not None:
- help_option_names = parent.help_option_names
- else:
- help_option_names = ["--help"]
-
- #: The names for the help options.
- self.help_option_names: list[str] = help_option_names
-
- if token_normalize_func is None and parent is not None:
- token_normalize_func = parent.token_normalize_func
-
- #: An optional normalization function for tokens. This is
- #: options, choices, commands etc.
- self.token_normalize_func: t.Callable[[str], str] | None = token_normalize_func
-
- #: Indicates if resilient parsing is enabled. In that case Click
- #: will do its best to not cause any failures and default values
- #: will be ignored. Useful for completion.
- self.resilient_parsing: bool = resilient_parsing
-
- # If there is no envvar prefix yet, but the parent has one and
- # the command on this level has a name, we can expand the envvar
- # prefix automatically.
- if auto_envvar_prefix is None:
- if (
- parent is not None
- and parent.auto_envvar_prefix is not None
- and self.info_name is not None
- ):
- auto_envvar_prefix = (
- f"{parent.auto_envvar_prefix}_{self.info_name.upper()}"
- )
- else:
- auto_envvar_prefix = auto_envvar_prefix.upper()
-
- if auto_envvar_prefix is not None:
- auto_envvar_prefix = auto_envvar_prefix.replace("-", "_")
-
- self.auto_envvar_prefix: str | None = auto_envvar_prefix
-
- if color is None and parent is not None:
- color = parent.color
-
- #: Controls if styling output is wanted or not.
- self.color: bool | None = color
-
- if show_default is None and parent is not None:
- show_default = parent.show_default
-
- #: Show option default values when formatting help text.
- self.show_default: bool | None = show_default
-
- self._close_callbacks: list[t.Callable[[], t.Any]] = []
- self._depth = 0
- self._parameter_source: dict[str, ParameterSource] = {}
- self._exit_stack = ExitStack()
-
- @property
- def protected_args(self) -> list[str]:
- import warnings
-
- warnings.warn(
- "'protected_args' is deprecated and will be removed in Click 9.0."
- " 'args' will contain remaining unparsed tokens.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._protected_args
-
- def to_info_dict(self) -> dict[str, t.Any]:
- """Gather information that could be useful for a tool generating
- user-facing documentation. This traverses the entire CLI
- structure.
-
- .. code-block:: python
-
- with Context(cli) as ctx:
- info = ctx.to_info_dict()
-
- .. versionadded:: 8.0
- """
- return {
- "command": self.command.to_info_dict(self),
- "info_name": self.info_name,
- "allow_extra_args": self.allow_extra_args,
- "allow_interspersed_args": self.allow_interspersed_args,
- "ignore_unknown_options": self.ignore_unknown_options,
- "auto_envvar_prefix": self.auto_envvar_prefix,
- }
-
- def __enter__(self) -> Context:
- self._depth += 1
- push_context(self)
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> bool | None:
- self._depth -= 1
- exit_result: bool | None = None
- if self._depth == 0:
- exit_result = self._close_with_exception_info(exc_type, exc_value, tb)
- pop_context()
-
- return exit_result
-
- @contextmanager
- def scope(self, cleanup: bool = True) -> cabc.Iterator[Context]:
- """This helper method can be used with the context object to promote
- it to the current thread local (see :func:`get_current_context`).
- The default behavior of this is to invoke the cleanup functions which
- can be disabled by setting `cleanup` to `False`. The cleanup
- functions are typically used for things such as closing file handles.
-
- If the cleanup is intended the context object can also be directly
- used as a context manager.
-
- Example usage::
-
- with ctx.scope():
- assert get_current_context() is ctx
-
- This is equivalent::
-
- with ctx:
- assert get_current_context() is ctx
-
- .. versionadded:: 5.0
-
- :param cleanup: controls if the cleanup functions should be run or
- not. The default is to run these functions. In
- some situations the context only wants to be
- temporarily pushed in which case this can be disabled.
- Nested pushes automatically defer the cleanup.
- """
- if not cleanup:
- self._depth += 1
- try:
- with self as rv:
- yield rv
- finally:
- if not cleanup:
- self._depth -= 1
-
- @property
- def meta(self) -> dict[str, t.Any]:
- """This is a dictionary which is shared with all the contexts
- that are nested. It exists so that click utilities can store some
- state here if they need to. It is however the responsibility of
- that code to manage this dictionary well.
-
- The keys are supposed to be unique dotted strings. For instance
- module paths are a good choice for it. What is stored in there is
- irrelevant for the operation of click. However what is important is
- that code that places data here adheres to the general semantics of
- the system.
-
- Example usage::
-
- LANG_KEY = f'{__name__}.lang'
-
- def set_language(value):
- ctx = get_current_context()
- ctx.meta[LANG_KEY] = value
-
- def get_language():
- return get_current_context().meta.get(LANG_KEY, 'en_US')
-
- .. versionadded:: 5.0
- """
- return self._meta
-
- def make_formatter(self) -> HelpFormatter:
- """Creates the :class:`~click.HelpFormatter` for the help and
- usage output.
-
- To quickly customize the formatter class used without overriding
- this method, set the :attr:`formatter_class` attribute.
-
- .. versionchanged:: 8.0
- Added the :attr:`formatter_class` attribute.
- """
- return self.formatter_class(
- width=self.terminal_width, max_width=self.max_content_width
- )
-
- def with_resource(self, context_manager: AbstractContextManager[V]) -> V:
- """Register a resource as if it were used in a ``with``
- statement. The resource will be cleaned up when the context is
- popped.
-
- Uses :meth:`contextlib.ExitStack.enter_context`. It calls the
- resource's ``__enter__()`` method and returns the result. When
- the context is popped, it closes the stack, which calls the
- resource's ``__exit__()`` method.
-
- To register a cleanup function for something that isn't a
- context manager, use :meth:`call_on_close`. Or use something
- from :mod:`contextlib` to turn it into a context manager first.
-
- .. code-block:: python
-
- @click.group()
- @click.option("--name")
- @click.pass_context
- def cli(ctx):
- ctx.obj = ctx.with_resource(connect_db(name))
-
- :param context_manager: The context manager to enter.
- :return: Whatever ``context_manager.__enter__()`` returns.
-
- .. versionadded:: 8.0
- """
- return self._exit_stack.enter_context(context_manager)
-
- def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
- """Register a function to be called when the context tears down.
-
- This can be used to close resources opened during the script
- execution. Resources that support Python's context manager
- protocol which would be used in a ``with`` statement should be
- registered with :meth:`with_resource` instead.
-
- :param f: The function to execute on teardown.
- """
- return self._exit_stack.callback(f)
-
- def close(self) -> None:
- """Invoke all close callbacks registered with
- :meth:`call_on_close`, and exit all context managers entered
- with :meth:`with_resource`.
- """
- self._close_with_exception_info(None, None, None)
-
- def _close_with_exception_info(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> bool | None:
- """Unwind the exit stack by calling its :meth:`__exit__` providing the exception
- information to allow for exception handling by the various resources registered
- using :meth;`with_resource`
-
- :return: Whatever ``exit_stack.__exit__()`` returns.
- """
- exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb)
- # In case the context is reused, create a new exit stack.
- self._exit_stack = ExitStack()
-
- return exit_result
-
- @property
- def command_path(self) -> str:
- """The computed command path. This is used for the ``usage``
- information on the help page. It's automatically created by
- combining the info names of the chain of contexts to the root.
- """
- rv = ""
- if self.info_name is not None:
- rv = self.info_name
- if self.parent is not None:
- parent_command_path = [self.parent.command_path]
-
- if isinstance(self.parent.command, Command):
- for param in self.parent.command.get_params(self):
- parent_command_path.extend(param.get_usage_pieces(self))
-
- rv = f"{' '.join(parent_command_path)} {rv}"
- return rv.lstrip()
-
- def find_root(self) -> Context:
- """Finds the outermost context."""
- node = self
- while node.parent is not None:
- node = node.parent
- return node
-
- def find_object(self, object_type: type[V]) -> V | None:
- """Finds the closest object of a given type."""
- node: Context | None = self
-
- while node is not None:
- if isinstance(node.obj, object_type):
- return node.obj
-
- node = node.parent
-
- return None
-
- def ensure_object(self, object_type: type[V]) -> V:
- """Like :meth:`find_object` but sets the innermost object to a
- new instance of `object_type` if it does not exist.
- """
- rv = self.find_object(object_type)
- if rv is None:
- self.obj = rv = object_type()
- return rv
-
- @t.overload
- def lookup_default(
- self, name: str, call: t.Literal[True] = True
- ) -> t.Any | None: ...
-
- @t.overload
- def lookup_default(
- self, name: str, call: t.Literal[False] = ...
- ) -> t.Any | t.Callable[[], t.Any] | None: ...
-
- def lookup_default(self, name: str, call: bool = True) -> t.Any | None:
- """Get the default for a parameter from :attr:`default_map`.
-
- :param name: Name of the parameter.
- :param call: If the default is a callable, call it. Disable to
- return the callable instead.
-
- .. versionchanged:: 8.0
- Added the ``call`` parameter.
- """
- if self.default_map is not None:
- value = self.default_map.get(name, UNSET)
-
- if call and callable(value):
- return value()
-
- return value
-
- return UNSET
-
- def fail(self, message: str) -> t.NoReturn:
- """Aborts the execution of the program with a specific error
- message.
-
- :param message: the error message to fail with.
- """
- raise UsageError(message, self)
-
- def abort(self) -> t.NoReturn:
- """Aborts the script."""
- raise Abort()
-
- def exit(self, code: int = 0) -> t.NoReturn:
- """Exits the application with a given exit code.
-
- .. versionchanged:: 8.2
- Callbacks and context managers registered with :meth:`call_on_close`
- and :meth:`with_resource` are closed before exiting.
- """
- self.close()
- raise Exit(code)
-
- def get_usage(self) -> str:
- """Helper method to get formatted usage string for the current
- context and command.
- """
- return self.command.get_usage(self)
-
- def get_help(self) -> str:
- """Helper method to get formatted help page for the current
- context and command.
- """
- return self.command.get_help(self)
-
- def _make_sub_context(self, command: Command) -> Context:
- """Create a new context of the same type as this context, but
- for a new command.
-
- :meta private:
- """
- return type(self)(command, info_name=command.name, parent=self)
-
- @t.overload
- def invoke(
- self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any
- ) -> V: ...
-
- @t.overload
- def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ...
-
- def invoke(
- self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any
- ) -> t.Any | V:
- """Invokes a command callback in exactly the way it expects. There
- are two ways to invoke this method:
-
- 1. the first argument can be a callback and all other arguments and
- keyword arguments are forwarded directly to the function.
- 2. the first argument is a click command object. In that case all
- arguments are forwarded as well but proper click parameters
- (options and click arguments) must be keyword arguments and Click
- will fill in defaults.
-
- .. versionchanged:: 8.0
- All ``kwargs`` are tracked in :attr:`params` so they will be
- passed if :meth:`forward` is called at multiple levels.
-
- .. versionchanged:: 3.2
- A new context is created, and missing arguments use default values.
- """
- if isinstance(callback, Command):
- other_cmd = callback
-
- if other_cmd.callback is None:
- raise TypeError(
- "The given command does not have a callback that can be invoked."
- )
- else:
- callback = t.cast("t.Callable[..., V]", other_cmd.callback)
-
- ctx = self._make_sub_context(other_cmd)
-
- for param in other_cmd.params:
- if param.name not in kwargs and param.expose_value:
- default_value = param.get_default(ctx)
- # We explicitly hide the :attr:`UNSET` value to the user, as we
- # choose to make it an implementation detail. And because ``invoke``
- # has been designed as part of Click public API, we return ``None``
- # instead. Refs:
- # https://github.com/pallets/click/issues/3066
- # https://github.com/pallets/click/issues/3065
- # https://github.com/pallets/click/pull/3068
- if default_value is UNSET:
- default_value = None
- kwargs[param.name] = param.type_cast_value( # type: ignore
- ctx, default_value
- )
-
- # Track all kwargs as params, so that forward() will pass
- # them on in subsequent calls.
- ctx.params.update(kwargs)
- else:
- ctx = self
-
- with augment_usage_errors(self):
- with ctx:
- return callback(*args, **kwargs)
-
- def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
- """Similar to :meth:`invoke` but fills in default keyword
- arguments from the current context if the other command expects
- it. This cannot invoke callbacks directly, only other commands.
-
- .. versionchanged:: 8.0
- All ``kwargs`` are tracked in :attr:`params` so they will be
- passed if ``forward`` is called at multiple levels.
- """
- # Can only forward to other commands, not direct callbacks.
- if not isinstance(cmd, Command):
- raise TypeError("Callback is not a command.")
-
- for param in self.params:
- if param not in kwargs:
- kwargs[param] = self.params[param]
-
- return self.invoke(cmd, *args, **kwargs)
-
- def set_parameter_source(self, name: str, source: ParameterSource) -> None:
- """Set the source of a parameter. This indicates the location
- from which the value of the parameter was obtained.
-
- :param name: The name of the parameter.
- :param source: A member of :class:`~click.core.ParameterSource`.
- """
- self._parameter_source[name] = source
-
- def get_parameter_source(self, name: str) -> ParameterSource | None:
- """Get the source of a parameter. This indicates the location
- from which the value of the parameter was obtained.
-
- This can be useful for determining when a user specified a value
- on the command line that is the same as the default value. It
- will be :attr:`~click.core.ParameterSource.DEFAULT` only if the
- value was actually taken from the default.
-
- :param name: The name of the parameter.
- :rtype: ParameterSource
-
- .. versionchanged:: 8.0
- Returns ``None`` if the parameter was not provided from any
- source.
- """
- return self._parameter_source.get(name)
-
-
-class Command:
- """Commands are the basic building block of command line interfaces in
- Click. A basic command handles command line parsing and might dispatch
- more parsing to commands nested below it.
-
- :param name: the name of the command to use unless a group overrides it.
- :param context_settings: an optional dictionary with defaults that are
- passed to the context object.
- :param callback: the callback to invoke. This is optional.
- :param params: the parameters to register with this command. This can
- be either :class:`Option` or :class:`Argument` objects.
- :param help: the help string to use for this command.
- :param epilog: like the help string but it's printed at the end of the
- help page after everything else.
- :param short_help: the short help to use for this command. This is
- shown on the command listing of the parent command.
- :param add_help_option: by default each command registers a ``--help``
- option. This can be disabled by this parameter.
- :param no_args_is_help: this controls what happens if no arguments are
- provided. This option is disabled by default.
- If enabled this will add ``--help`` as argument
- if no arguments are passed
- :param hidden: hide this command from help outputs.
- :param deprecated: If ``True`` or non-empty string, issues a message
- indicating that the command is deprecated and highlights
- its deprecation in --help. The message can be customized
- by using a string as the value.
-
- .. versionchanged:: 8.2
- This is the base class for all commands, not ``BaseCommand``.
- ``deprecated`` can be set to a string as well to customize the
- deprecation message.
-
- .. versionchanged:: 8.1
- ``help``, ``epilog``, and ``short_help`` are stored unprocessed,
- all formatting is done when outputting help text, not at init,
- and is done even if not using the ``@command`` decorator.
-
- .. versionchanged:: 8.0
- Added a ``repr`` showing the command name.
-
- .. versionchanged:: 7.1
- Added the ``no_args_is_help`` parameter.
-
- .. versionchanged:: 2.0
- Added the ``context_settings`` parameter.
- """
-
- #: The context class to create with :meth:`make_context`.
- #:
- #: .. versionadded:: 8.0
- context_class: type[Context] = Context
-
- #: the default for the :attr:`Context.allow_extra_args` flag.
- allow_extra_args = False
-
- #: the default for the :attr:`Context.allow_interspersed_args` flag.
- allow_interspersed_args = True
-
- #: the default for the :attr:`Context.ignore_unknown_options` flag.
- ignore_unknown_options = False
-
- def __init__(
- self,
- name: str | None,
- context_settings: cabc.MutableMapping[str, t.Any] | None = None,
- callback: t.Callable[..., t.Any] | None = None,
- params: list[Parameter] | None = None,
- help: str | None = None,
- epilog: str | None = None,
- short_help: str | None = None,
- options_metavar: str | None = "[OPTIONS]",
- add_help_option: bool = True,
- no_args_is_help: bool = False,
- hidden: bool = False,
- deprecated: bool | str = False,
- ) -> None:
- #: the name the command thinks it has. Upon registering a command
- #: on a :class:`Group` the group will default the command name
- #: with this information. You should instead use the
- #: :class:`Context`\'s :attr:`~Context.info_name` attribute.
- self.name = name
-
- if context_settings is None:
- context_settings = {}
-
- #: an optional dictionary with defaults passed to the context.
- self.context_settings: cabc.MutableMapping[str, t.Any] = context_settings
-
- #: the callback to execute when the command fires. This might be
- #: `None` in which case nothing happens.
- self.callback = callback
- #: the list of parameters for this command in the order they
- #: should show up in the help page and execute. Eager parameters
- #: will automatically be handled before non eager ones.
- self.params: list[Parameter] = params or []
- self.help = help
- self.epilog = epilog
- self.options_metavar = options_metavar
- self.short_help = short_help
- self.add_help_option = add_help_option
- self._help_option = None
- self.no_args_is_help = no_args_is_help
- self.hidden = hidden
- self.deprecated = deprecated
-
- def to_info_dict(self, ctx: Context) -> dict[str, t.Any]:
- return {
- "name": self.name,
- "params": [param.to_info_dict() for param in self.get_params(ctx)],
- "help": self.help,
- "epilog": self.epilog,
- "short_help": self.short_help,
- "hidden": self.hidden,
- "deprecated": self.deprecated,
- }
-
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__} {self.name}>"
-
- def get_usage(self, ctx: Context) -> str:
- """Formats the usage line into a string and returns it.
-
- Calls :meth:`format_usage` internally.
- """
- formatter = ctx.make_formatter()
- self.format_usage(ctx, formatter)
- return formatter.getvalue().rstrip("\n")
-
- def get_params(self, ctx: Context) -> list[Parameter]:
- params = self.params
- help_option = self.get_help_option(ctx)
-
- if help_option is not None:
- params = [*params, help_option]
-
- if __debug__:
- import warnings
-
- opts = [opt for param in params for opt in param.opts]
- opts_counter = Counter(opts)
- duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1)
-
- for duplicate_opt in duplicate_opts:
- warnings.warn(
- (
- f"The parameter {duplicate_opt} is used more than once. "
- "Remove its duplicate as parameters should be unique."
- ),
- stacklevel=3,
- )
-
- return params
-
- def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Writes the usage line into the formatter.
-
- This is a low-level method called by :meth:`get_usage`.
- """
- pieces = self.collect_usage_pieces(ctx)
- formatter.write_usage(ctx.command_path, " ".join(pieces))
-
- def collect_usage_pieces(self, ctx: Context) -> list[str]:
- """Returns all the pieces that go into the usage line and returns
- it as a list of strings.
- """
- rv = [self.options_metavar] if self.options_metavar else []
-
- for param in self.get_params(ctx):
- rv.extend(param.get_usage_pieces(ctx))
-
- return rv
-
- def get_help_option_names(self, ctx: Context) -> list[str]:
- """Returns the names for the help option."""
- all_names = set(ctx.help_option_names)
- for param in self.params:
- all_names.difference_update(param.opts)
- all_names.difference_update(param.secondary_opts)
- return list(all_names)
-
- def get_help_option(self, ctx: Context) -> Option | None:
- """Returns the help option object.
-
- Skipped if :attr:`add_help_option` is ``False``.
-
- .. versionchanged:: 8.1.8
- The help option is now cached to avoid creating it multiple times.
- """
- help_option_names = self.get_help_option_names(ctx)
-
- if not help_option_names or not self.add_help_option:
- return None
-
- # Cache the help option object in private _help_option attribute to
- # avoid creating it multiple times. Not doing this will break the
- # callback odering by iter_params_for_processing(), which relies on
- # object comparison.
- if self._help_option is None:
- # Avoid circular import.
- from .decorators import help_option
-
- # Apply help_option decorator and pop resulting option
- help_option(*help_option_names)(self)
- self._help_option = self.params.pop() # type: ignore[assignment]
-
- return self._help_option
-
- def make_parser(self, ctx: Context) -> _OptionParser:
- """Creates the underlying option parser for this command."""
- parser = _OptionParser(ctx)
- for param in self.get_params(ctx):
- param.add_to_parser(parser, ctx)
- return parser
-
- def get_help(self, ctx: Context) -> str:
- """Formats the help into a string and returns it.
-
- Calls :meth:`format_help` internally.
- """
- formatter = ctx.make_formatter()
- self.format_help(ctx, formatter)
- return formatter.getvalue().rstrip("\n")
-
- def get_short_help_str(self, limit: int = 45) -> str:
- """Gets short help for the command or makes it by shortening the
- long help string.
- """
- if self.short_help:
- text = inspect.cleandoc(self.short_help)
- elif self.help:
- text = make_default_short_help(self.help, limit)
- else:
- text = ""
-
- if self.deprecated:
- deprecated_message = (
- f"(DEPRECATED: {self.deprecated})"
- if isinstance(self.deprecated, str)
- else "(DEPRECATED)"
- )
- text = _("{text} {deprecated_message}").format(
- text=text, deprecated_message=deprecated_message
- )
-
- return text.strip()
-
- def format_help(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Writes the help into the formatter if it exists.
-
- This is a low-level method called by :meth:`get_help`.
-
- This calls the following methods:
-
- - :meth:`format_usage`
- - :meth:`format_help_text`
- - :meth:`format_options`
- - :meth:`format_epilog`
- """
- self.format_usage(ctx, formatter)
- self.format_help_text(ctx, formatter)
- self.format_options(ctx, formatter)
- self.format_epilog(ctx, formatter)
-
- def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Writes the help text to the formatter if it exists."""
- if self.help is not None:
- # truncate the help text to the first form feed
- text = inspect.cleandoc(self.help).partition("\f")[0]
- else:
- text = ""
-
- if self.deprecated:
- deprecated_message = (
- f"(DEPRECATED: {self.deprecated})"
- if isinstance(self.deprecated, str)
- else "(DEPRECATED)"
- )
- text = _("{text} {deprecated_message}").format(
- text=text, deprecated_message=deprecated_message
- )
-
- if text:
- formatter.write_paragraph()
-
- with formatter.indentation():
- formatter.write_text(text)
-
- def format_options(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Writes all the options into the formatter if they exist."""
- opts = []
- for param in self.get_params(ctx):
- rv = param.get_help_record(ctx)
- if rv is not None:
- opts.append(rv)
-
- if opts:
- with formatter.section(_("Options")):
- formatter.write_dl(opts)
-
- def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Writes the epilog into the formatter if it exists."""
- if self.epilog:
- epilog = inspect.cleandoc(self.epilog)
- formatter.write_paragraph()
-
- with formatter.indentation():
- formatter.write_text(epilog)
-
- def make_context(
- self,
- info_name: str | None,
- args: list[str],
- parent: Context | None = None,
- **extra: t.Any,
- ) -> Context:
- """This function when given an info name and arguments will kick
- off the parsing and create a new :class:`Context`. It does not
- invoke the actual command callback though.
-
- To quickly customize the context class used without overriding
- this method, set the :attr:`context_class` attribute.
-
- :param info_name: the info name for this invocation. Generally this
- is the most descriptive name for the script or
- command. For the toplevel script it's usually
- the name of the script, for commands below it's
- the name of the command.
- :param args: the arguments to parse as list of strings.
- :param parent: the parent context if available.
- :param extra: extra keyword arguments forwarded to the context
- constructor.
-
- .. versionchanged:: 8.0
- Added the :attr:`context_class` attribute.
- """
- for key, value in self.context_settings.items():
- if key not in extra:
- extra[key] = value
-
- ctx = self.context_class(self, info_name=info_name, parent=parent, **extra)
-
- with ctx.scope(cleanup=False):
- self.parse_args(ctx, args)
- return ctx
-
- def parse_args(self, ctx: Context, args: list[str]) -> list[str]:
- if not args and self.no_args_is_help and not ctx.resilient_parsing:
- raise NoArgsIsHelpError(ctx)
-
- parser = self.make_parser(ctx)
- opts, args, param_order = parser.parse_args(args=args)
-
- for param in iter_params_for_processing(param_order, self.get_params(ctx)):
- _, args = param.handle_parse_result(ctx, opts, args)
-
- # We now have all parameters' values into `ctx.params`, but the data may contain
- # the `UNSET` sentinel.
- # Convert `UNSET` to `None` to ensure that the user doesn't see `UNSET`.
- #
- # Waiting until after the initial parse to convert allows us to treat `UNSET`
- # more like a missing value when multiple params use the same name.
- # Refs:
- # https://github.com/pallets/click/issues/3071
- # https://github.com/pallets/click/pull/3079
- for name, value in ctx.params.items():
- if value is UNSET:
- ctx.params[name] = None
-
- if args and not ctx.allow_extra_args and not ctx.resilient_parsing:
- ctx.fail(
- ngettext(
- "Got unexpected extra argument ({args})",
- "Got unexpected extra arguments ({args})",
- len(args),
- ).format(args=" ".join(map(str, args)))
- )
-
- ctx.args = args
- ctx._opt_prefixes.update(parser._opt_prefixes)
- return args
-
- def invoke(self, ctx: Context) -> t.Any:
- """Given a context, this invokes the attached callback (if it exists)
- in the right way.
- """
- if self.deprecated:
- extra_message = (
- f" {self.deprecated}" if isinstance(self.deprecated, str) else ""
- )
- message = _(
- "DeprecationWarning: The command {name!r} is deprecated.{extra_message}"
- ).format(name=self.name, extra_message=extra_message)
- echo(style(message, fg="red"), err=True)
-
- if self.callback is not None:
- return ctx.invoke(self.callback, **ctx.params)
-
- def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]:
- """Return a list of completions for the incomplete value. Looks
- at the names of options and chained multi-commands.
-
- Any command could be part of a chained multi-command, so sibling
- commands are valid at any point during command completion.
-
- :param ctx: Invocation context for this command.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
-
- results: list[CompletionItem] = []
-
- if incomplete and not incomplete[0].isalnum():
- for param in self.get_params(ctx):
- if (
- not isinstance(param, Option)
- or param.hidden
- or (
- not param.multiple
- and ctx.get_parameter_source(param.name) # type: ignore
- is ParameterSource.COMMANDLINE
- )
- ):
- continue
-
- results.extend(
- CompletionItem(name, help=param.help)
- for name in [*param.opts, *param.secondary_opts]
- if name.startswith(incomplete)
- )
-
- while ctx.parent is not None:
- ctx = ctx.parent
-
- if isinstance(ctx.command, Group) and ctx.command.chain:
- results.extend(
- CompletionItem(name, help=command.get_short_help_str())
- for name, command in _complete_visible_commands(ctx, incomplete)
- if name not in ctx._protected_args
- )
-
- return results
-
- @t.overload
- def main(
- self,
- args: cabc.Sequence[str] | None = None,
- prog_name: str | None = None,
- complete_var: str | None = None,
- standalone_mode: t.Literal[True] = True,
- **extra: t.Any,
- ) -> t.NoReturn: ...
-
- @t.overload
- def main(
- self,
- args: cabc.Sequence[str] | None = None,
- prog_name: str | None = None,
- complete_var: str | None = None,
- standalone_mode: bool = ...,
- **extra: t.Any,
- ) -> t.Any: ...
-
- def main(
- self,
- args: cabc.Sequence[str] | None = None,
- prog_name: str | None = None,
- complete_var: str | None = None,
- standalone_mode: bool = True,
- windows_expand_args: bool = True,
- **extra: t.Any,
- ) -> t.Any:
- """This is the way to invoke a script with all the bells and
- whistles as a command line application. This will always terminate
- the application after a call. If this is not wanted, ``SystemExit``
- needs to be caught.
-
- This method is also available by directly calling the instance of
- a :class:`Command`.
-
- :param args: the arguments that should be used for parsing. If not
- provided, ``sys.argv[1:]`` is used.
- :param prog_name: the program name that should be used. By default
- the program name is constructed by taking the file
- name from ``sys.argv[0]``.
- :param complete_var: the environment variable that controls the
- bash completion support. The default is
- ``"__COMPLETE"`` with prog_name in
- uppercase.
- :param standalone_mode: the default behavior is to invoke the script
- in standalone mode. Click will then
- handle exceptions and convert them into
- error messages and the function will never
- return but shut down the interpreter. If
- this is set to `False` they will be
- propagated to the caller and the return
- value of this function is the return value
- of :meth:`invoke`.
- :param windows_expand_args: Expand glob patterns, user dir, and
- env vars in command line args on Windows.
- :param extra: extra keyword arguments are forwarded to the context
- constructor. See :class:`Context` for more information.
-
- .. versionchanged:: 8.0.1
- Added the ``windows_expand_args`` parameter to allow
- disabling command line arg expansion on Windows.
-
- .. versionchanged:: 8.0
- When taking arguments from ``sys.argv`` on Windows, glob
- patterns, user dir, and env vars are expanded.
-
- .. versionchanged:: 3.0
- Added the ``standalone_mode`` parameter.
- """
- if args is None:
- args = sys.argv[1:]
-
- if os.name == "nt" and windows_expand_args:
- args = _expand_args(args)
- else:
- args = list(args)
-
- if prog_name is None:
- prog_name = _detect_program_name()
-
- # Process shell completion requests and exit early.
- self._main_shell_completion(extra, prog_name, complete_var)
-
- try:
- try:
- with self.make_context(prog_name, args, **extra) as ctx:
- rv = self.invoke(ctx)
- if not standalone_mode:
- return rv
- # it's not safe to `ctx.exit(rv)` here!
- # note that `rv` may actually contain data like "1" which
- # has obvious effects
- # more subtle case: `rv=[None, None]` can come out of
- # chained commands which all returned `None` -- so it's not
- # even always obvious that `rv` indicates success/failure
- # by its truthiness/falsiness
- ctx.exit()
- except (EOFError, KeyboardInterrupt) as e:
- echo(file=sys.stderr)
- raise Abort() from e
- except ClickException as e:
- if not standalone_mode:
- raise
- e.show()
- sys.exit(e.exit_code)
- except OSError as e:
- if e.errno == errno.EPIPE:
- sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout))
- sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr))
- sys.exit(1)
- else:
- raise
- except Exit as e:
- if standalone_mode:
- sys.exit(e.exit_code)
- else:
- # in non-standalone mode, return the exit code
- # note that this is only reached if `self.invoke` above raises
- # an Exit explicitly -- thus bypassing the check there which
- # would return its result
- # the results of non-standalone execution may therefore be
- # somewhat ambiguous: if there are codepaths which lead to
- # `ctx.exit(1)` and to `return 1`, the caller won't be able to
- # tell the difference between the two
- return e.exit_code
- except Abort:
- if not standalone_mode:
- raise
- echo(_("Aborted!"), file=sys.stderr)
- sys.exit(1)
-
- def _main_shell_completion(
- self,
- ctx_args: cabc.MutableMapping[str, t.Any],
- prog_name: str,
- complete_var: str | None = None,
- ) -> None:
- """Check if the shell is asking for tab completion, process
- that, then exit early. Called from :meth:`main` before the
- program is invoked.
-
- :param prog_name: Name of the executable in the shell.
- :param complete_var: Name of the environment variable that holds
- the completion instruction. Defaults to
- ``_{PROG_NAME}_COMPLETE``.
-
- .. versionchanged:: 8.2.0
- Dots (``.``) in ``prog_name`` are replaced with underscores (``_``).
- """
- if complete_var is None:
- complete_name = prog_name.replace("-", "_").replace(".", "_")
- complete_var = f"_{complete_name}_COMPLETE".upper()
-
- instruction = os.environ.get(complete_var)
-
- if not instruction:
- return
-
- from .shell_completion import shell_complete
-
- rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction)
- sys.exit(rv)
-
- def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- """Alias for :meth:`main`."""
- return self.main(*args, **kwargs)
-
-
-class _FakeSubclassCheck(type):
- def __subclasscheck__(cls, subclass: type) -> bool:
- return issubclass(subclass, cls.__bases__[0])
-
- def __instancecheck__(cls, instance: t.Any) -> bool:
- return isinstance(instance, cls.__bases__[0])
-
-
-class _BaseCommand(Command, metaclass=_FakeSubclassCheck):
- """
- .. deprecated:: 8.2
- Will be removed in Click 9.0. Use ``Command`` instead.
- """
-
-
-class Group(Command):
- """A group is a command that nests other commands (or more groups).
-
- :param name: The name of the group command.
- :param commands: Map names to :class:`Command` objects. Can be a list, which
- will use :attr:`Command.name` as the keys.
- :param invoke_without_command: Invoke the group's callback even if a
- subcommand is not given.
- :param no_args_is_help: If no arguments are given, show the group's help and
- exit. Defaults to the opposite of ``invoke_without_command``.
- :param subcommand_metavar: How to represent the subcommand argument in help.
- The default will represent whether ``chain`` is set or not.
- :param chain: Allow passing more than one subcommand argument. After parsing
- a command's arguments, if any arguments remain another command will be
- matched, and so on.
- :param result_callback: A function to call after the group's and
- subcommand's callbacks. The value returned by the subcommand is passed.
- If ``chain`` is enabled, the value will be a list of values returned by
- all the commands. If ``invoke_without_command`` is enabled, the value
- will be the value returned by the group's callback, or an empty list if
- ``chain`` is enabled.
- :param kwargs: Other arguments passed to :class:`Command`.
-
- .. versionchanged:: 8.0
- The ``commands`` argument can be a list of command objects.
-
- .. versionchanged:: 8.2
- Merged with and replaces the ``MultiCommand`` base class.
- """
-
- allow_extra_args = True
- allow_interspersed_args = False
-
- #: If set, this is used by the group's :meth:`command` decorator
- #: as the default :class:`Command` class. This is useful to make all
- #: subcommands use a custom command class.
- #:
- #: .. versionadded:: 8.0
- command_class: type[Command] | None = None
-
- #: If set, this is used by the group's :meth:`group` decorator
- #: as the default :class:`Group` class. This is useful to make all
- #: subgroups use a custom group class.
- #:
- #: If set to the special value :class:`type` (literally
- #: ``group_class = type``), this group's class will be used as the
- #: default class. This makes a custom group class continue to make
- #: custom groups.
- #:
- #: .. versionadded:: 8.0
- group_class: type[Group] | type[type] | None = None
- # Literal[type] isn't valid, so use Type[type]
-
- def __init__(
- self,
- name: str | None = None,
- commands: cabc.MutableMapping[str, Command]
- | cabc.Sequence[Command]
- | None = None,
- invoke_without_command: bool = False,
- no_args_is_help: bool | None = None,
- subcommand_metavar: str | None = None,
- chain: bool = False,
- result_callback: t.Callable[..., t.Any] | None = None,
- **kwargs: t.Any,
- ) -> None:
- super().__init__(name, **kwargs)
-
- if commands is None:
- commands = {}
- elif isinstance(commands, abc.Sequence):
- commands = {c.name: c for c in commands if c.name is not None}
-
- #: The registered subcommands by their exported names.
- self.commands: cabc.MutableMapping[str, Command] = commands
-
- if no_args_is_help is None:
- no_args_is_help = not invoke_without_command
-
- self.no_args_is_help = no_args_is_help
- self.invoke_without_command = invoke_without_command
-
- if subcommand_metavar is None:
- if chain:
- subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..."
- else:
- subcommand_metavar = "COMMAND [ARGS]..."
-
- self.subcommand_metavar = subcommand_metavar
- self.chain = chain
- # The result callback that is stored. This can be set or
- # overridden with the :func:`result_callback` decorator.
- self._result_callback = result_callback
-
- if self.chain:
- for param in self.params:
- if isinstance(param, Argument) and not param.required:
- raise RuntimeError(
- "A group in chain mode cannot have optional arguments."
- )
-
- def to_info_dict(self, ctx: Context) -> dict[str, t.Any]:
- info_dict = super().to_info_dict(ctx)
- commands = {}
-
- for name in self.list_commands(ctx):
- command = self.get_command(ctx, name)
-
- if command is None:
- continue
-
- sub_ctx = ctx._make_sub_context(command)
-
- with sub_ctx.scope(cleanup=False):
- commands[name] = command.to_info_dict(sub_ctx)
-
- info_dict.update(commands=commands, chain=self.chain)
- return info_dict
-
- def add_command(self, cmd: Command, name: str | None = None) -> None:
- """Registers another :class:`Command` with this group. If the name
- is not provided, the name of the command is used.
- """
- name = name or cmd.name
- if name is None:
- raise TypeError("Command has no name.")
- _check_nested_chain(self, name, cmd, register=True)
- self.commands[name] = cmd
-
- @t.overload
- def command(self, __func: t.Callable[..., t.Any]) -> Command: ...
-
- @t.overload
- def command(
- self, *args: t.Any, **kwargs: t.Any
- ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ...
-
- def command(
- self, *args: t.Any, **kwargs: t.Any
- ) -> t.Callable[[t.Callable[..., t.Any]], Command] | Command:
- """A shortcut decorator for declaring and attaching a command to
- the group. This takes the same arguments as :func:`command` and
- immediately registers the created command with this group by
- calling :meth:`add_command`.
-
- To customize the command class used, set the
- :attr:`command_class` attribute.
-
- .. versionchanged:: 8.1
- This decorator can be applied without parentheses.
-
- .. versionchanged:: 8.0
- Added the :attr:`command_class` attribute.
- """
- from .decorators import command
-
- func: t.Callable[..., t.Any] | None = None
-
- if args and callable(args[0]):
- assert len(args) == 1 and not kwargs, (
- "Use 'command(**kwargs)(callable)' to provide arguments."
- )
- (func,) = args
- args = ()
-
- if self.command_class and kwargs.get("cls") is None:
- kwargs["cls"] = self.command_class
-
- def decorator(f: t.Callable[..., t.Any]) -> Command:
- cmd: Command = command(*args, **kwargs)(f)
- self.add_command(cmd)
- return cmd
-
- if func is not None:
- return decorator(func)
-
- return decorator
-
- @t.overload
- def group(self, __func: t.Callable[..., t.Any]) -> Group: ...
-
- @t.overload
- def group(
- self, *args: t.Any, **kwargs: t.Any
- ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ...
-
- def group(
- self, *args: t.Any, **kwargs: t.Any
- ) -> t.Callable[[t.Callable[..., t.Any]], Group] | Group:
- """A shortcut decorator for declaring and attaching a group to
- the group. This takes the same arguments as :func:`group` and
- immediately registers the created group with this group by
- calling :meth:`add_command`.
-
- To customize the group class used, set the :attr:`group_class`
- attribute.
-
- .. versionchanged:: 8.1
- This decorator can be applied without parentheses.
-
- .. versionchanged:: 8.0
- Added the :attr:`group_class` attribute.
- """
- from .decorators import group
-
- func: t.Callable[..., t.Any] | None = None
-
- if args and callable(args[0]):
- assert len(args) == 1 and not kwargs, (
- "Use 'group(**kwargs)(callable)' to provide arguments."
- )
- (func,) = args
- args = ()
-
- if self.group_class is not None and kwargs.get("cls") is None:
- if self.group_class is type:
- kwargs["cls"] = type(self)
- else:
- kwargs["cls"] = self.group_class
-
- def decorator(f: t.Callable[..., t.Any]) -> Group:
- cmd: Group = group(*args, **kwargs)(f)
- self.add_command(cmd)
- return cmd
-
- if func is not None:
- return decorator(func)
-
- return decorator
-
- def result_callback(self, replace: bool = False) -> t.Callable[[F], F]:
- """Adds a result callback to the command. By default if a
- result callback is already registered this will chain them but
- this can be disabled with the `replace` parameter. The result
- callback is invoked with the return value of the subcommand
- (or the list of return values from all subcommands if chaining
- is enabled) as well as the parameters as they would be passed
- to the main callback.
-
- Example::
-
- @click.group()
- @click.option('-i', '--input', default=23)
- def cli(input):
- return 42
-
- @cli.result_callback()
- def process_result(result, input):
- return result + input
-
- :param replace: if set to `True` an already existing result
- callback will be removed.
-
- .. versionchanged:: 8.0
- Renamed from ``resultcallback``.
-
- .. versionadded:: 3.0
- """
-
- def decorator(f: F) -> F:
- old_callback = self._result_callback
-
- if old_callback is None or replace:
- self._result_callback = f
- return f
-
- def function(value: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
- inner = old_callback(value, *args, **kwargs)
- return f(inner, *args, **kwargs)
-
- self._result_callback = rv = update_wrapper(t.cast(F, function), f)
- return rv # type: ignore[return-value]
-
- return decorator
-
- def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
- """Given a context and a command name, this returns a :class:`Command`
- object if it exists or returns ``None``.
- """
- return self.commands.get(cmd_name)
-
- def list_commands(self, ctx: Context) -> list[str]:
- """Returns a list of subcommand names in the order they should appear."""
- return sorted(self.commands)
-
- def collect_usage_pieces(self, ctx: Context) -> list[str]:
- rv = super().collect_usage_pieces(ctx)
- rv.append(self.subcommand_metavar)
- return rv
-
- def format_options(self, ctx: Context, formatter: HelpFormatter) -> None:
- super().format_options(ctx, formatter)
- self.format_commands(ctx, formatter)
-
- def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
- """Extra format methods for multi methods that adds all the commands
- after the options.
- """
- commands = []
- for subcommand in self.list_commands(ctx):
- cmd = self.get_command(ctx, subcommand)
- # What is this, the tool lied about a command. Ignore it
- if cmd is None:
- continue
- if cmd.hidden:
- continue
-
- commands.append((subcommand, cmd))
-
- # allow for 3 times the default spacing
- if len(commands):
- limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
-
- rows = []
- for subcommand, cmd in commands:
- help = cmd.get_short_help_str(limit)
- rows.append((subcommand, help))
-
- if rows:
- with formatter.section(_("Commands")):
- formatter.write_dl(rows)
-
- def parse_args(self, ctx: Context, args: list[str]) -> list[str]:
- if not args and self.no_args_is_help and not ctx.resilient_parsing:
- raise NoArgsIsHelpError(ctx)
-
- rest = super().parse_args(ctx, args)
-
- if self.chain:
- ctx._protected_args = rest
- ctx.args = []
- elif rest:
- ctx._protected_args, ctx.args = rest[:1], rest[1:]
-
- return ctx.args
-
- def invoke(self, ctx: Context) -> t.Any:
- def _process_result(value: t.Any) -> t.Any:
- if self._result_callback is not None:
- value = ctx.invoke(self._result_callback, value, **ctx.params)
- return value
-
- if not ctx._protected_args:
- if self.invoke_without_command:
- # No subcommand was invoked, so the result callback is
- # invoked with the group return value for regular
- # groups, or an empty list for chained groups.
- with ctx:
- rv = super().invoke(ctx)
- return _process_result([] if self.chain else rv)
- ctx.fail(_("Missing command."))
-
- # Fetch args back out
- args = [*ctx._protected_args, *ctx.args]
- ctx.args = []
- ctx._protected_args = []
-
- # If we're not in chain mode, we only allow the invocation of a
- # single command but we also inform the current context about the
- # name of the command to invoke.
- if not self.chain:
- # Make sure the context is entered so we do not clean up
- # resources until the result processor has worked.
- with ctx:
- cmd_name, cmd, args = self.resolve_command(ctx, args)
- assert cmd is not None
- ctx.invoked_subcommand = cmd_name
- super().invoke(ctx)
- sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
- with sub_ctx:
- return _process_result(sub_ctx.command.invoke(sub_ctx))
-
- # In chain mode we create the contexts step by step, but after the
- # base command has been invoked. Because at that point we do not
- # know the subcommands yet, the invoked subcommand attribute is
- # set to ``*`` to inform the command that subcommands are executed
- # but nothing else.
- with ctx:
- ctx.invoked_subcommand = "*" if args else None
- super().invoke(ctx)
-
- # Otherwise we make every single context and invoke them in a
- # chain. In that case the return value to the result processor
- # is the list of all invoked subcommand's results.
- contexts = []
- while args:
- cmd_name, cmd, args = self.resolve_command(ctx, args)
- assert cmd is not None
- sub_ctx = cmd.make_context(
- cmd_name,
- args,
- parent=ctx,
- allow_extra_args=True,
- allow_interspersed_args=False,
- )
- contexts.append(sub_ctx)
- args, sub_ctx.args = sub_ctx.args, []
-
- rv = []
- for sub_ctx in contexts:
- with sub_ctx:
- rv.append(sub_ctx.command.invoke(sub_ctx))
- return _process_result(rv)
-
- def resolve_command(
- self, ctx: Context, args: list[str]
- ) -> tuple[str | None, Command | None, list[str]]:
- cmd_name = make_str(args[0])
- original_cmd_name = cmd_name
-
- # Get the command
- cmd = self.get_command(ctx, cmd_name)
-
- # If we can't find the command but there is a normalization
- # function available, we try with that one.
- if cmd is None and ctx.token_normalize_func is not None:
- cmd_name = ctx.token_normalize_func(cmd_name)
- cmd = self.get_command(ctx, cmd_name)
-
- # If we don't find the command we want to show an error message
- # to the user that it was not provided. However, there is
- # something else we should do: if the first argument looks like
- # an option we want to kick off parsing again for arguments to
- # resolve things like --help which now should go to the main
- # place.
- if cmd is None and not ctx.resilient_parsing:
- if _split_opt(cmd_name)[0]:
- self.parse_args(ctx, args)
- ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name))
- return cmd_name if cmd else None, cmd, args[1:]
-
- def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]:
- """Return a list of completions for the incomplete value. Looks
- at the names of options, subcommands, and chained
- multi-commands.
-
- :param ctx: Invocation context for this command.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
-
- results = [
- CompletionItem(name, help=command.get_short_help_str())
- for name, command in _complete_visible_commands(ctx, incomplete)
- ]
- results.extend(super().shell_complete(ctx, incomplete))
- return results
-
-
-class _MultiCommand(Group, metaclass=_FakeSubclassCheck):
- """
- .. deprecated:: 8.2
- Will be removed in Click 9.0. Use ``Group`` instead.
- """
-
-
-class CommandCollection(Group):
- """A :class:`Group` that looks up subcommands on other groups. If a command
- is not found on this group, each registered source is checked in order.
- Parameters on a source are not added to this group, and a source's callback
- is not invoked when invoking its commands. In other words, this "flattens"
- commands in many groups into this one group.
-
- :param name: The name of the group command.
- :param sources: A list of :class:`Group` objects to look up commands from.
- :param kwargs: Other arguments passed to :class:`Group`.
-
- .. versionchanged:: 8.2
- This is a subclass of ``Group``. Commands are looked up first on this
- group, then each of its sources.
- """
-
- def __init__(
- self,
- name: str | None = None,
- sources: list[Group] | None = None,
- **kwargs: t.Any,
- ) -> None:
- super().__init__(name, **kwargs)
- #: The list of registered groups.
- self.sources: list[Group] = sources or []
-
- def add_source(self, group: Group) -> None:
- """Add a group as a source of commands."""
- self.sources.append(group)
-
- def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
- rv = super().get_command(ctx, cmd_name)
-
- if rv is not None:
- return rv
-
- for source in self.sources:
- rv = source.get_command(ctx, cmd_name)
-
- if rv is not None:
- if self.chain:
- _check_nested_chain(self, cmd_name, rv)
-
- return rv
-
- return None
-
- def list_commands(self, ctx: Context) -> list[str]:
- rv: set[str] = set(super().list_commands(ctx))
-
- for source in self.sources:
- rv.update(source.list_commands(ctx))
-
- return sorted(rv)
-
-
-def _check_iter(value: t.Any) -> cabc.Iterator[t.Any]:
- """Check if the value is iterable but not a string. Raises a type
- error, or return an iterator over the value.
- """
- if isinstance(value, str):
- raise TypeError
-
- return iter(value)
-
-
-class Parameter:
- r"""A parameter to a command comes in two versions: they are either
- :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
- not supported by design as some of the internals for parsing are
- intentionally not finalized.
-
- Some settings are supported by both options and arguments.
-
- :param param_decls: the parameter declarations for this option or
- argument. This is a list of flags or argument
- names.
- :param type: the type that should be used. Either a :class:`ParamType`
- or a Python type. The latter is converted into the former
- automatically if supported.
- :param required: controls if this is optional or not.
- :param default: the default value if omitted. This can also be a callable,
- in which case it's invoked when the default is needed
- without any arguments.
- :param callback: A function to further process or validate the value
- after type conversion. It is called as ``f(ctx, param, value)``
- and must return the value. It is called for all sources,
- including prompts.
- :param nargs: the number of arguments to match. If not ``1`` the return
- value is a tuple instead of single value. The default for
- nargs is ``1`` (except if the type is a tuple, then it's
- the arity of the tuple). If ``nargs=-1``, all remaining
- parameters are collected.
- :param metavar: how the value is represented in the help page.
- :param expose_value: if this is `True` then the value is passed onwards
- to the command callback and stored on the context,
- otherwise it's skipped.
- :param is_eager: eager values are processed before non eager ones. This
- should not be set for arguments or it will inverse the
- order of processing.
- :param envvar: environment variable(s) that are used to provide a default value for
- this parameter. This can be a string or a sequence of strings. If a sequence is
- given, only the first non-empty environment variable is used for the parameter.
- :param shell_complete: A function that returns custom shell
- completions. Used instead of the param's type completion if
- given. Takes ``ctx, param, incomplete`` and must return a list
- of :class:`~click.shell_completion.CompletionItem` or a list of
- strings.
- :param deprecated: If ``True`` or non-empty string, issues a message
- indicating that the argument is deprecated and highlights
- its deprecation in --help. The message can be customized
- by using a string as the value. A deprecated parameter
- cannot be required, a ValueError will be raised otherwise.
-
- .. versionchanged:: 8.2.0
- Introduction of ``deprecated``.
-
- .. versionchanged:: 8.2
- Adding duplicate parameter names to a :class:`~click.core.Command` will
- result in a ``UserWarning`` being shown.
-
- .. versionchanged:: 8.2
- Adding duplicate parameter names to a :class:`~click.core.Command` will
- result in a ``UserWarning`` being shown.
-
- .. versionchanged:: 8.0
- ``process_value`` validates required parameters and bounded
- ``nargs``, and invokes the parameter callback before returning
- the value. This allows the callback to validate prompts.
- ``full_process_value`` is removed.
-
- .. versionchanged:: 8.0
- ``autocompletion`` is renamed to ``shell_complete`` and has new
- semantics described above. The old name is deprecated and will
- be removed in 8.1, until then it will be wrapped to match the
- new requirements.
-
- .. versionchanged:: 8.0
- For ``multiple=True, nargs>1``, the default must be a list of
- tuples.
-
- .. versionchanged:: 8.0
- Setting a default is no longer required for ``nargs>1``, it will
- default to ``None``. ``multiple=True`` or ``nargs=-1`` will
- default to ``()``.
-
- .. versionchanged:: 7.1
- Empty environment variables are ignored rather than taking the
- empty string value. This makes it possible for scripts to clear
- variables if they can't unset them.
-
- .. versionchanged:: 2.0
- Changed signature for parameter callback to also be passed the
- parameter. The old callback format will still work, but it will
- raise a warning to give you a chance to migrate the code easier.
- """
-
- param_type_name = "parameter"
-
- def __init__(
- self,
- param_decls: cabc.Sequence[str] | None = None,
- type: types.ParamType | t.Any | None = None,
- required: bool = False,
- # XXX The default historically embed two concepts:
- # - the declaration of a Parameter object carrying the default (handy to
- # arbitrage the default value of coupled Parameters sharing the same
- # self.name, like flag options),
- # - and the actual value of the default.
- # It is confusing and is the source of many issues discussed in:
- # https://github.com/pallets/click/pull/3030
- # In the future, we might think of splitting it in two, not unlike
- # Option.is_flag and Option.flag_value: we could have something like
- # Parameter.is_default and Parameter.default_value.
- default: t.Any | t.Callable[[], t.Any] | None = UNSET,
- callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None,
- nargs: int | None = None,
- multiple: bool = False,
- metavar: str | None = None,
- expose_value: bool = True,
- is_eager: bool = False,
- envvar: str | cabc.Sequence[str] | None = None,
- shell_complete: t.Callable[
- [Context, Parameter, str], list[CompletionItem] | list[str]
- ]
- | None = None,
- deprecated: bool | str = False,
- ) -> None:
- self.name: str | None
- self.opts: list[str]
- self.secondary_opts: list[str]
- self.name, self.opts, self.secondary_opts = self._parse_decls(
- param_decls or (), expose_value
- )
- self.type: types.ParamType = types.convert_type(type, default)
-
- # Default nargs to what the type tells us if we have that
- # information available.
- if nargs is None:
- if self.type.is_composite:
- nargs = self.type.arity
- else:
- nargs = 1
-
- self.required = required
- self.callback = callback
- self.nargs = nargs
- self.multiple = multiple
- self.expose_value = expose_value
- self.default: t.Any | t.Callable[[], t.Any] | None = default
- self.is_eager = is_eager
- self.metavar = metavar
- self.envvar = envvar
- self._custom_shell_complete = shell_complete
- self.deprecated = deprecated
-
- if __debug__:
- if self.type.is_composite and nargs != self.type.arity:
- raise ValueError(
- f"'nargs' must be {self.type.arity} (or None) for"
- f" type {self.type!r}, but it was {nargs}."
- )
-
- if required and deprecated:
- raise ValueError(
- f"The {self.param_type_name} '{self.human_readable_name}' "
- "is deprecated and still required. A deprecated "
- f"{self.param_type_name} cannot be required."
- )
-
- def to_info_dict(self) -> dict[str, t.Any]:
- """Gather information that could be useful for a tool generating
- user-facing documentation.
-
- Use :meth:`click.Context.to_info_dict` to traverse the entire
- CLI structure.
-
- .. versionchanged:: 8.3.0
- Returns ``None`` for the :attr:`default` if it was not set.
-
- .. versionadded:: 8.0
- """
- return {
- "name": self.name,
- "param_type_name": self.param_type_name,
- "opts": self.opts,
- "secondary_opts": self.secondary_opts,
- "type": self.type.to_info_dict(),
- "required": self.required,
- "nargs": self.nargs,
- "multiple": self.multiple,
- # We explicitly hide the :attr:`UNSET` value to the user, as we choose to
- # make it an implementation detail. And because ``to_info_dict`` has been
- # designed for documentation purposes, we return ``None`` instead.
- "default": self.default if self.default is not UNSET else None,
- "envvar": self.envvar,
- }
-
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__} {self.name}>"
-
- def _parse_decls(
- self, decls: cabc.Sequence[str], expose_value: bool
- ) -> tuple[str | None, list[str], list[str]]:
- raise NotImplementedError()
-
- @property
- def human_readable_name(self) -> str:
- """Returns the human readable name of this parameter. This is the
- same as the name for options, but the metavar for arguments.
- """
- return self.name # type: ignore
-
- def make_metavar(self, ctx: Context) -> str:
- if self.metavar is not None:
- return self.metavar
-
- metavar = self.type.get_metavar(param=self, ctx=ctx)
-
- if metavar is None:
- metavar = self.type.name.upper()
-
- if self.nargs != 1:
- metavar += "..."
-
- return metavar
-
- @t.overload
- def get_default(
- self, ctx: Context, call: t.Literal[True] = True
- ) -> t.Any | None: ...
-
- @t.overload
- def get_default(
- self, ctx: Context, call: bool = ...
- ) -> t.Any | t.Callable[[], t.Any] | None: ...
-
- def get_default(
- self, ctx: Context, call: bool = True
- ) -> t.Any | t.Callable[[], t.Any] | None:
- """Get the default for the parameter. Tries
- :meth:`Context.lookup_default` first, then the local default.
-
- :param ctx: Current context.
- :param call: If the default is a callable, call it. Disable to
- return the callable instead.
-
- .. versionchanged:: 8.0.2
- Type casting is no longer performed when getting a default.
-
- .. versionchanged:: 8.0.1
- Type casting can fail in resilient parsing mode. Invalid
- defaults will not prevent showing help text.
-
- .. versionchanged:: 8.0
- Looks at ``ctx.default_map`` first.
-
- .. versionchanged:: 8.0
- Added the ``call`` parameter.
- """
- value = ctx.lookup_default(self.name, call=False) # type: ignore
-
- if value is UNSET:
- value = self.default
-
- if call and callable(value):
- value = value()
-
- return value
-
- def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None:
- raise NotImplementedError()
-
- def consume_value(
- self, ctx: Context, opts: cabc.Mapping[str, t.Any]
- ) -> tuple[t.Any, ParameterSource]:
- """Returns the parameter value produced by the parser.
-
- If the parser did not produce a value from user input, the value is either
- sourced from the environment variable, the default map, or the parameter's
- default value. In that order of precedence.
-
- If no value is found, an internal sentinel value is returned.
-
- :meta private:
- """
- # Collect from the parse the value passed by the user to the CLI.
- value = opts.get(self.name, UNSET) # type: ignore
- # If the value is set, it means it was sourced from the command line by the
- # parser, otherwise it left unset by default.
- source = (
- ParameterSource.COMMANDLINE
- if value is not UNSET
- else ParameterSource.DEFAULT
- )
-
- if value is UNSET:
- envvar_value = self.value_from_envvar(ctx)
- if envvar_value is not None:
- value = envvar_value
- source = ParameterSource.ENVIRONMENT
-
- if value is UNSET:
- default_map_value = ctx.lookup_default(self.name) # type: ignore
- if default_map_value is not UNSET:
- value = default_map_value
- source = ParameterSource.DEFAULT_MAP
-
- if value is UNSET:
- default_value = self.get_default(ctx)
- if default_value is not UNSET:
- value = default_value
- source = ParameterSource.DEFAULT
-
- return value, source
-
- def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any:
- """Convert and validate a value against the parameter's
- :attr:`type`, :attr:`multiple`, and :attr:`nargs`.
- """
- if value is None:
- if self.multiple or self.nargs == -1:
- return ()
- else:
- return value
-
- def check_iter(value: t.Any) -> cabc.Iterator[t.Any]:
- try:
- return _check_iter(value)
- except TypeError:
- # This should only happen when passing in args manually,
- # the parser should construct an iterable when parsing
- # the command line.
- raise BadParameter(
- _("Value must be an iterable."), ctx=ctx, param=self
- ) from None
-
- # Define the conversion function based on nargs and type.
-
- if self.nargs == 1 or self.type.is_composite:
-
- def convert(value: t.Any) -> t.Any:
- return self.type(value, param=self, ctx=ctx)
-
- elif self.nargs == -1:
-
- def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...]
- return tuple(self.type(x, self, ctx) for x in check_iter(value))
-
- else: # nargs > 1
-
- def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...]
- value = tuple(check_iter(value))
-
- if len(value) != self.nargs:
- raise BadParameter(
- ngettext(
- "Takes {nargs} values but 1 was given.",
- "Takes {nargs} values but {len} were given.",
- len(value),
- ).format(nargs=self.nargs, len=len(value)),
- ctx=ctx,
- param=self,
- )
-
- return tuple(self.type(x, self, ctx) for x in value)
-
- if self.multiple:
- return tuple(convert(x) for x in check_iter(value))
-
- return convert(value)
-
- def value_is_missing(self, value: t.Any) -> bool:
- """A value is considered missing if:
-
- - it is :attr:`UNSET`,
- - or if it is an empty sequence while the parameter is suppose to have
- non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is
- set).
-
- :meta private:
- """
- if value is UNSET:
- return True
-
- if (self.nargs != 1 or self.multiple) and value == ():
- return True
-
- return False
-
- def process_value(self, ctx: Context, value: t.Any) -> t.Any:
- """Process the value of this parameter:
-
- 1. Type cast the value using :meth:`type_cast_value`.
- 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise
- :exc:`MissingParameter` if it is required.
- 3. If a :attr:`callback` is set, call it to have the value replaced by the
- result of the callback. If the value was not set, the callback receive
- ``None``. This keep the legacy behavior as it was before the introduction of
- the :attr:`UNSET` sentinel.
-
- :meta private:
- """
- # shelter `type_cast_value` from ever seeing an `UNSET` value by handling the
- # cases in which `UNSET` gets special treatment explicitly at this layer
- #
- # Refs:
- # https://github.com/pallets/click/issues/3069
- if value is UNSET:
- if self.multiple or self.nargs == -1:
- value = ()
- else:
- value = self.type_cast_value(ctx, value)
-
- if self.required and self.value_is_missing(value):
- raise MissingParameter(ctx=ctx, param=self)
-
- if self.callback is not None:
- # Legacy case: UNSET is not exposed directly to the callback, but converted
- # to None.
- if value is UNSET:
- value = None
-
- # Search for parameters with UNSET values in the context.
- unset_keys = {k: None for k, v in ctx.params.items() if v is UNSET}
- # No UNSET values, call the callback as usual.
- if not unset_keys:
- value = self.callback(ctx, self, value)
-
- # Legacy case: provide a temporarily manipulated context to the callback
- # to hide UNSET values as None.
- #
- # Refs:
- # https://github.com/pallets/click/issues/3136
- # https://github.com/pallets/click/pull/3137
- else:
- # Add another layer to the context stack to clearly hint that the
- # context is temporarily modified.
- with ctx:
- # Update the context parameters to replace UNSET with None.
- ctx.params.update(unset_keys)
- # Feed these fake context parameters to the callback.
- value = self.callback(ctx, self, value)
- # Restore the UNSET values in the context parameters.
- ctx.params.update(
- {
- k: UNSET
- for k in unset_keys
- # Only restore keys that are present and still None, in case
- # the callback modified other parameters.
- if k in ctx.params and ctx.params[k] is None
- }
- )
-
- return value
-
- def resolve_envvar_value(self, ctx: Context) -> str | None:
- """Returns the value found in the environment variable(s) attached to this
- parameter.
-
- Environment variables values are `always returned as strings
- `_.
-
- This method returns ``None`` if:
-
- - the :attr:`envvar` property is not set on the :class:`Parameter`,
- - the environment variable is not found in the environment,
- - the variable is found in the environment but its value is empty (i.e. the
- environment variable is present but has an empty string).
-
- If :attr:`envvar` is setup with multiple environment variables,
- then only the first non-empty value is returned.
-
- .. caution::
-
- The raw value extracted from the environment is not normalized and is
- returned as-is. Any normalization or reconciliation is performed later by
- the :class:`Parameter`'s :attr:`type`.
-
- :meta private:
- """
- if not self.envvar:
- return None
-
- if isinstance(self.envvar, str):
- rv = os.environ.get(self.envvar)
-
- if rv:
- return rv
- else:
- for envvar in self.envvar:
- rv = os.environ.get(envvar)
-
- # Return the first non-empty value of the list of environment variables.
- if rv:
- return rv
- # Else, absence of value is interpreted as an environment variable that
- # is not set, so proceed to the next one.
-
- return None
-
- def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None:
- """Process the raw environment variable string for this parameter.
-
- Returns the string as-is or splits it into a sequence of strings if the
- parameter is expecting multiple values (i.e. its :attr:`nargs` property is set
- to a value other than ``1``).
-
- :meta private:
- """
- rv = self.resolve_envvar_value(ctx)
-
- if rv is not None and self.nargs != 1:
- return self.type.split_envvar_value(rv)
-
- return rv
-
- def handle_parse_result(
- self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str]
- ) -> tuple[t.Any, list[str]]:
- """Process the value produced by the parser from user input.
-
- Always process the value through the Parameter's :attr:`type`, wherever it
- comes from.
-
- If the parameter is deprecated, this method warn the user about it. But only if
- the value has been explicitly set by the user (and as such, is not coming from
- a default).
-
- :meta private:
- """
- with augment_usage_errors(ctx, param=self):
- value, source = self.consume_value(ctx, opts)
-
- ctx.set_parameter_source(self.name, source) # type: ignore
-
- # Display a deprecation warning if necessary.
- if (
- self.deprecated
- and value is not UNSET
- and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
- ):
- extra_message = (
- f" {self.deprecated}" if isinstance(self.deprecated, str) else ""
- )
- message = _(
- "DeprecationWarning: The {param_type} {name!r} is deprecated."
- "{extra_message}"
- ).format(
- param_type=self.param_type_name,
- name=self.human_readable_name,
- extra_message=extra_message,
- )
- echo(style(message, fg="red"), err=True)
-
- # Process the value through the parameter's type.
- try:
- value = self.process_value(ctx, value)
- except Exception:
- if not ctx.resilient_parsing:
- raise
- # In resilient parsing mode, we do not want to fail the command if the
- # value is incompatible with the parameter type, so we reset the value
- # to UNSET, which will be interpreted as a missing value.
- value = UNSET
-
- # Add parameter's value to the context.
- if (
- self.expose_value
- # We skip adding the value if it was previously set by another parameter
- # targeting the same variable name. This prevents parameters competing for
- # the same name to override each other.
- and (self.name not in ctx.params or ctx.params[self.name] is UNSET)
- ):
- # Click is logically enforcing that the name is None if the parameter is
- # not to be exposed. We still assert it here to please the type checker.
- assert self.name is not None, (
- f"{self!r} parameter's name should not be None when exposing value."
- )
- ctx.params[self.name] = value
-
- return value, args
-
- def get_help_record(self, ctx: Context) -> tuple[str, str] | None:
- pass
-
- def get_usage_pieces(self, ctx: Context) -> list[str]:
- return []
-
- def get_error_hint(self, ctx: Context) -> str:
- """Get a stringified version of the param for use in error messages to
- indicate which param caused the error.
- """
- hint_list = self.opts or [self.human_readable_name]
- return " / ".join(f"'{x}'" for x in hint_list)
-
- def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]:
- """Return a list of completions for the incomplete value. If a
- ``shell_complete`` function was given during init, it is used.
- Otherwise, the :attr:`type`
- :meth:`~click.types.ParamType.shell_complete` function is used.
-
- :param ctx: Invocation context for this command.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- if self._custom_shell_complete is not None:
- results = self._custom_shell_complete(ctx, self, incomplete)
-
- if results and isinstance(results[0], str):
- from click.shell_completion import CompletionItem
-
- results = [CompletionItem(c) for c in results]
-
- return t.cast("list[CompletionItem]", results)
-
- return self.type.shell_complete(ctx, self, incomplete)
-
-
-class Option(Parameter):
- """Options are usually optional values on the command line and
- have some extra features that arguments don't have.
-
- All other parameters are passed onwards to the parameter constructor.
-
- :param show_default: Show the default value for this option in its
- help text. Values are not shown by default, unless
- :attr:`Context.show_default` is ``True``. If this value is a
- string, it shows that string in parentheses instead of the
- actual value. This is particularly useful for dynamic options.
- For single option boolean flags, the default remains hidden if
- its value is ``False``.
- :param show_envvar: Controls if an environment variable should be
- shown on the help page and error messages.
- Normally, environment variables are not shown.
- :param prompt: If set to ``True`` or a non empty string then the
- user will be prompted for input. If set to ``True`` the prompt
- will be the option name capitalized. A deprecated option cannot be
- prompted.
- :param confirmation_prompt: Prompt a second time to confirm the
- value if it was prompted for. Can be set to a string instead of
- ``True`` to customize the message.
- :param prompt_required: If set to ``False``, the user will be
- prompted for input only when the option was specified as a flag
- without a value.
- :param hide_input: If this is ``True`` then the input on the prompt
- will be hidden from the user. This is useful for password input.
- :param is_flag: forces this option to act as a flag. The default is
- auto detection.
- :param flag_value: which value should be used for this flag if it's
- enabled. This is set to a boolean automatically if
- the option string contains a slash to mark two options.
- :param multiple: if this is set to `True` then the argument is accepted
- multiple times and recorded. This is similar to ``nargs``
- in how it works but supports arbitrary number of
- arguments.
- :param count: this flag makes an option increment an integer.
- :param allow_from_autoenv: if this is enabled then the value of this
- parameter will be pulled from an environment
- variable in case a prefix is defined on the
- context.
- :param help: the help string.
- :param hidden: hide this option from help outputs.
- :param attrs: Other command arguments described in :class:`Parameter`.
-
- .. versionchanged:: 8.2
- ``envvar`` used with ``flag_value`` will always use the ``flag_value``,
- previously it would use the value of the environment variable.
-
- .. versionchanged:: 8.1
- Help text indentation is cleaned here instead of only in the
- ``@option`` decorator.
-
- .. versionchanged:: 8.1
- The ``show_default`` parameter overrides
- ``Context.show_default``.
-
- .. versionchanged:: 8.1
- The default of a single option boolean flag is not shown if the
- default value is ``False``.
-
- .. versionchanged:: 8.0.1
- ``type`` is detected from ``flag_value`` if given.
- """
-
- param_type_name = "option"
-
- def __init__(
- self,
- param_decls: cabc.Sequence[str] | None = None,
- show_default: bool | str | None = None,
- prompt: bool | str = False,
- confirmation_prompt: bool | str = False,
- prompt_required: bool = True,
- hide_input: bool = False,
- is_flag: bool | None = None,
- flag_value: t.Any = UNSET,
- multiple: bool = False,
- count: bool = False,
- allow_from_autoenv: bool = True,
- type: types.ParamType | t.Any | None = None,
- help: str | None = None,
- hidden: bool = False,
- show_choices: bool = True,
- show_envvar: bool = False,
- deprecated: bool | str = False,
- **attrs: t.Any,
- ) -> None:
- if help:
- help = inspect.cleandoc(help)
-
- super().__init__(
- param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs
- )
-
- if prompt is True:
- if self.name is None:
- raise TypeError("'name' is required with 'prompt=True'.")
-
- prompt_text: str | None = self.name.replace("_", " ").capitalize()
- elif prompt is False:
- prompt_text = None
- else:
- prompt_text = prompt
-
- if deprecated:
- deprecated_message = (
- f"(DEPRECATED: {deprecated})"
- if isinstance(deprecated, str)
- else "(DEPRECATED)"
- )
- help = help + deprecated_message if help is not None else deprecated_message
-
- self.prompt = prompt_text
- self.confirmation_prompt = confirmation_prompt
- self.prompt_required = prompt_required
- self.hide_input = hide_input
- self.hidden = hidden
-
- # The _flag_needs_value property tells the parser that this option is a flag
- # that cannot be used standalone and needs a value. With this information, the
- # parser can determine whether to consider the next user-provided argument in
- # the CLI as a value for this flag or as a new option.
- # If prompt is enabled but not required, then it opens the possibility for the
- # option to gets its value from the user.
- self._flag_needs_value = self.prompt is not None and not self.prompt_required
-
- # Auto-detect if this is a flag or not.
- if is_flag is None:
- # Implicitly a flag because flag_value was set.
- if flag_value is not UNSET:
- is_flag = True
- # Not a flag, but when used as a flag it shows a prompt.
- elif self._flag_needs_value:
- is_flag = False
- # Implicitly a flag because secondary options names were given.
- elif self.secondary_opts:
- is_flag = True
- # The option is explicitly not a flag. But we do not know yet if it needs a
- # value or not. So we look at the default value to determine it.
- elif is_flag is False and not self._flag_needs_value:
- self._flag_needs_value = self.default is UNSET
-
- if is_flag:
- # Set missing default for flags if not explicitly required or prompted.
- if self.default is UNSET and not self.required and not self.prompt:
- if multiple:
- self.default = ()
-
- # Auto-detect the type of the flag based on the flag_value.
- if type is None:
- # A flag without a flag_value is a boolean flag.
- if flag_value is UNSET:
- self.type: types.ParamType = types.BoolParamType()
- # If the flag value is a boolean, use BoolParamType.
- elif isinstance(flag_value, bool):
- self.type = types.BoolParamType()
- # Otherwise, guess the type from the flag value.
- else:
- self.type = types.convert_type(None, flag_value)
-
- self.is_flag: bool = bool(is_flag)
- self.is_bool_flag: bool = bool(
- is_flag and isinstance(self.type, types.BoolParamType)
- )
- self.flag_value: t.Any = flag_value
-
- # Set boolean flag default to False if unset and not required.
- if self.is_bool_flag:
- if self.default is UNSET and not self.required:
- self.default = False
-
- # Support the special case of aligning the default value with the flag_value
- # for flags whose default is explicitly set to True. Note that as long as we
- # have this condition, there is no way a flag can have a default set to True,
- # and a flag_value set to something else. Refs:
- # https://github.com/pallets/click/issues/3024#issuecomment-3146199461
- # https://github.com/pallets/click/pull/3030/commits/06847da
- if self.default is True and self.flag_value is not UNSET:
- self.default = self.flag_value
-
- # Set the default flag_value if it is not set.
- if self.flag_value is UNSET:
- if self.is_flag:
- self.flag_value = True
- else:
- self.flag_value = None
-
- # Counting.
- self.count = count
- if count:
- if type is None:
- self.type = types.IntRange(min=0)
- if self.default is UNSET:
- self.default = 0
-
- self.allow_from_autoenv = allow_from_autoenv
- self.help = help
- self.show_default = show_default
- self.show_choices = show_choices
- self.show_envvar = show_envvar
-
- if __debug__:
- if deprecated and prompt:
- raise ValueError("`deprecated` options cannot use `prompt`.")
-
- if self.nargs == -1:
- raise TypeError("nargs=-1 is not supported for options.")
-
- if not self.is_bool_flag and self.secondary_opts:
- raise TypeError("Secondary flag is not valid for non-boolean flag.")
-
- if self.is_bool_flag and self.hide_input and self.prompt is not None:
- raise TypeError(
- "'prompt' with 'hide_input' is not valid for boolean flag."
- )
-
- if self.count:
- if self.multiple:
- raise TypeError("'count' is not valid with 'multiple'.")
-
- if self.is_flag:
- raise TypeError("'count' is not valid with 'is_flag'.")
-
- def to_info_dict(self) -> dict[str, t.Any]:
- """
- .. versionchanged:: 8.3.0
- Returns ``None`` for the :attr:`flag_value` if it was not set.
- """
- info_dict = super().to_info_dict()
- info_dict.update(
- help=self.help,
- prompt=self.prompt,
- is_flag=self.is_flag,
- # We explicitly hide the :attr:`UNSET` value to the user, as we choose to
- # make it an implementation detail. And because ``to_info_dict`` has been
- # designed for documentation purposes, we return ``None`` instead.
- flag_value=self.flag_value if self.flag_value is not UNSET else None,
- count=self.count,
- hidden=self.hidden,
- )
- return info_dict
-
- def get_error_hint(self, ctx: Context) -> str:
- result = super().get_error_hint(ctx)
- if self.show_envvar and self.envvar is not None:
- result += f" (env var: '{self.envvar}')"
- return result
-
- def _parse_decls(
- self, decls: cabc.Sequence[str], expose_value: bool
- ) -> tuple[str | None, list[str], list[str]]:
- opts = []
- secondary_opts = []
- name = None
- possible_names = []
-
- for decl in decls:
- if decl.isidentifier():
- if name is not None:
- raise TypeError(f"Name '{name}' defined twice")
- name = decl
- else:
- split_char = ";" if decl[:1] == "/" else "/"
- if split_char in decl:
- first, second = decl.split(split_char, 1)
- first = first.rstrip()
- if first:
- possible_names.append(_split_opt(first))
- opts.append(first)
- second = second.lstrip()
- if second:
- secondary_opts.append(second.lstrip())
- if first == second:
- raise ValueError(
- f"Boolean option {decl!r} cannot use the"
- " same flag for true/false."
- )
- else:
- possible_names.append(_split_opt(decl))
- opts.append(decl)
-
- if name is None and possible_names:
- possible_names.sort(key=lambda x: -len(x[0])) # group long options first
- name = possible_names[0][1].replace("-", "_").lower()
- if not name.isidentifier():
- name = None
-
- if name is None:
- if not expose_value:
- return None, opts, secondary_opts
- raise TypeError(
- f"Could not determine name for option with declarations {decls!r}"
- )
-
- if not opts and not secondary_opts:
- raise TypeError(
- f"No options defined but a name was passed ({name})."
- " Did you mean to declare an argument instead? Did"
- f" you mean to pass '--{name}'?"
- )
-
- return name, opts, secondary_opts
-
- def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None:
- if self.multiple:
- action = "append"
- elif self.count:
- action = "count"
- else:
- action = "store"
-
- if self.is_flag:
- action = f"{action}_const"
-
- if self.is_bool_flag and self.secondary_opts:
- parser.add_option(
- obj=self, opts=self.opts, dest=self.name, action=action, const=True
- )
- parser.add_option(
- obj=self,
- opts=self.secondary_opts,
- dest=self.name,
- action=action,
- const=False,
- )
- else:
- parser.add_option(
- obj=self,
- opts=self.opts,
- dest=self.name,
- action=action,
- const=self.flag_value,
- )
- else:
- parser.add_option(
- obj=self,
- opts=self.opts,
- dest=self.name,
- action=action,
- nargs=self.nargs,
- )
-
- def get_help_record(self, ctx: Context) -> tuple[str, str] | None:
- if self.hidden:
- return None
-
- any_prefix_is_slash = False
-
- def _write_opts(opts: cabc.Sequence[str]) -> str:
- nonlocal any_prefix_is_slash
-
- rv, any_slashes = join_options(opts)
-
- if any_slashes:
- any_prefix_is_slash = True
-
- if not self.is_flag and not self.count:
- rv += f" {self.make_metavar(ctx=ctx)}"
-
- return rv
-
- rv = [_write_opts(self.opts)]
-
- if self.secondary_opts:
- rv.append(_write_opts(self.secondary_opts))
-
- help = self.help or ""
-
- extra = self.get_help_extra(ctx)
- extra_items = []
- if "envvars" in extra:
- extra_items.append(
- _("env var: {var}").format(var=", ".join(extra["envvars"]))
- )
- if "default" in extra:
- extra_items.append(_("default: {default}").format(default=extra["default"]))
- if "range" in extra:
- extra_items.append(extra["range"])
- if "required" in extra:
- extra_items.append(_(extra["required"]))
-
- if extra_items:
- extra_str = "; ".join(extra_items)
- help = f"{help} [{extra_str}]" if help else f"[{extra_str}]"
-
- return ("; " if any_prefix_is_slash else " / ").join(rv), help
-
- def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra:
- extra: types.OptionHelpExtra = {}
-
- if self.show_envvar:
- envvar = self.envvar
-
- if envvar is None:
- if (
- self.allow_from_autoenv
- and ctx.auto_envvar_prefix is not None
- and self.name is not None
- ):
- envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
-
- if envvar is not None:
- if isinstance(envvar, str):
- extra["envvars"] = (envvar,)
- else:
- extra["envvars"] = tuple(str(d) for d in envvar)
-
- # Temporarily enable resilient parsing to avoid type casting
- # failing for the default. Might be possible to extend this to
- # help formatting in general.
- resilient = ctx.resilient_parsing
- ctx.resilient_parsing = True
-
- try:
- default_value = self.get_default(ctx, call=False)
- finally:
- ctx.resilient_parsing = resilient
-
- show_default = False
- show_default_is_str = False
-
- if self.show_default is not None:
- if isinstance(self.show_default, str):
- show_default_is_str = show_default = True
- else:
- show_default = self.show_default
- elif ctx.show_default is not None:
- show_default = ctx.show_default
-
- if show_default_is_str or (
- show_default and (default_value not in (None, UNSET))
- ):
- if show_default_is_str:
- default_string = f"({self.show_default})"
- elif isinstance(default_value, (list, tuple)):
- default_string = ", ".join(str(d) for d in default_value)
- elif isinstance(default_value, enum.Enum):
- default_string = default_value.name
- elif inspect.isfunction(default_value):
- default_string = _("(dynamic)")
- elif self.is_bool_flag and self.secondary_opts:
- # For boolean flags that have distinct True/False opts,
- # use the opt without prefix instead of the value.
- default_string = _split_opt(
- (self.opts if default_value else self.secondary_opts)[0]
- )[1]
- elif self.is_bool_flag and not self.secondary_opts and not default_value:
- default_string = ""
- elif default_value == "":
- default_string = '""'
- else:
- default_string = str(default_value)
-
- if default_string:
- extra["default"] = default_string
-
- if (
- isinstance(self.type, types._NumberRangeBase)
- # skip count with default range type
- and not (self.count and self.type.min == 0 and self.type.max is None)
- ):
- range_str = self.type._describe_range()
-
- if range_str:
- extra["range"] = range_str
-
- if self.required:
- extra["required"] = "required"
-
- return extra
-
- def prompt_for_value(self, ctx: Context) -> t.Any:
- """This is an alternative flow that can be activated in the full
- value processing if a value does not exist. It will prompt the
- user until a valid value exists and then returns the processed
- value as result.
- """
- assert self.prompt is not None
-
- # Calculate the default before prompting anything to lock in the value before
- # attempting any user interaction.
- default = self.get_default(ctx)
-
- # A boolean flag can use a simplified [y/n] confirmation prompt.
- if self.is_bool_flag:
- # If we have no boolean default, we force the user to explicitly provide
- # one.
- if default in (UNSET, None):
- default = None
- # Nothing prevent you to declare an option that is simultaneously:
- # 1) auto-detected as a boolean flag,
- # 2) allowed to prompt, and
- # 3) still declare a non-boolean default.
- # This forced casting into a boolean is necessary to align any non-boolean
- # default to the prompt, which is going to be a [y/n]-style confirmation
- # because the option is still a boolean flag. That way, instead of [y/n],
- # we get [Y/n] or [y/N] depending on the truthy value of the default.
- # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249
- else:
- default = bool(default)
- return confirm(self.prompt, default)
-
- # If show_default is set to True/False, provide this to `prompt` as well. For
- # non-bool values of `show_default`, we use `prompt`'s default behavior
- prompt_kwargs: t.Any = {}
- if isinstance(self.show_default, bool):
- prompt_kwargs["show_default"] = self.show_default
-
- return prompt(
- self.prompt,
- # Use ``None`` to inform the prompt() function to reiterate until a valid
- # value is provided by the user if we have no default.
- default=None if default is UNSET else default,
- type=self.type,
- hide_input=self.hide_input,
- show_choices=self.show_choices,
- confirmation_prompt=self.confirmation_prompt,
- value_proc=lambda x: self.process_value(ctx, x),
- **prompt_kwargs,
- )
-
- def resolve_envvar_value(self, ctx: Context) -> str | None:
- """:class:`Option` resolves its environment variable the same way as
- :func:`Parameter.resolve_envvar_value`, but it also supports
- :attr:`Context.auto_envvar_prefix`. If we could not find an environment from
- the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix`
- to build dynamiccaly the environment variable name using the
- :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template.
-
- :meta private:
- """
- rv = super().resolve_envvar_value(ctx)
-
- if rv is not None:
- return rv
-
- if (
- self.allow_from_autoenv
- and ctx.auto_envvar_prefix is not None
- and self.name is not None
- ):
- envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
- rv = os.environ.get(envvar)
-
- if rv:
- return rv
-
- return None
-
- def value_from_envvar(self, ctx: Context) -> t.Any:
- """For :class:`Option`, this method processes the raw environment variable
- string the same way as :func:`Parameter.value_from_envvar` does.
-
- But in the case of non-boolean flags, the value is analyzed to determine if the
- flag is activated or not, and returns a boolean of its activation, or the
- :attr:`flag_value` if the latter is set.
-
- This method also takes care of repeated options (i.e. options with
- :attr:`multiple` set to ``True``).
-
- :meta private:
- """
- rv = self.resolve_envvar_value(ctx)
-
- # Absent environment variable or an empty string is interpreted as unset.
- if rv is None:
- return None
-
- # Non-boolean flags are more liberal in what they accept. But a flag being a
- # flag, its envvar value still needs to be analyzed to determine if the flag is
- # activated or not.
- if self.is_flag and not self.is_bool_flag:
- # If the flag_value is set and match the envvar value, return it
- # directly.
- if self.flag_value is not UNSET and rv == self.flag_value:
- return self.flag_value
- # Analyze the envvar value as a boolean to know if the flag is
- # activated or not.
- return types.BoolParamType.str_to_bool(rv)
-
- # Split the envvar value if it is allowed to be repeated.
- value_depth = (self.nargs != 1) + bool(self.multiple)
- if value_depth > 0:
- multi_rv = self.type.split_envvar_value(rv)
- if self.multiple and self.nargs != 1:
- multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment]
-
- return multi_rv
-
- return rv
-
- def consume_value(
- self, ctx: Context, opts: cabc.Mapping[str, Parameter]
- ) -> tuple[t.Any, ParameterSource]:
- """For :class:`Option`, the value can be collected from an interactive prompt
- if the option is a flag that needs a value (and the :attr:`prompt` property is
- set).
-
- Additionally, this method handles flag option that are activated without a
- value, in which case the :attr:`flag_value` is returned.
-
- :meta private:
- """
- value, source = super().consume_value(ctx, opts)
-
- # The parser will emit a sentinel value if the option is allowed to as a flag
- # without a value.
- if value is FLAG_NEEDS_VALUE:
- # If the option allows for a prompt, we start an interaction with the user.
- if self.prompt is not None and not ctx.resilient_parsing:
- value = self.prompt_for_value(ctx)
- source = ParameterSource.PROMPT
- # Else the flag takes its flag_value as value.
- else:
- value = self.flag_value
- source = ParameterSource.COMMANDLINE
-
- # A flag which is activated always returns the flag value, unless the value
- # comes from the explicitly sets default.
- elif (
- self.is_flag
- and value is True
- and not self.is_bool_flag
- and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
- ):
- value = self.flag_value
-
- # Re-interpret a multiple option which has been sent as-is by the parser.
- # Here we replace each occurrence of value-less flags (marked by the
- # FLAG_NEEDS_VALUE sentinel) with the flag_value.
- elif (
- self.multiple
- and value is not UNSET
- and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
- and any(v is FLAG_NEEDS_VALUE for v in value)
- ):
- value = [self.flag_value if v is FLAG_NEEDS_VALUE else v for v in value]
- source = ParameterSource.COMMANDLINE
-
- # The value wasn't set, or used the param's default, prompt for one to the user
- # if prompting is enabled.
- elif (
- (
- value is UNSET
- or source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP)
- )
- and self.prompt is not None
- and (self.required or self.prompt_required)
- and not ctx.resilient_parsing
- ):
- value = self.prompt_for_value(ctx)
- source = ParameterSource.PROMPT
-
- return value, source
-
- def process_value(self, ctx: Context, value: t.Any) -> t.Any:
- # process_value has to be overridden on Options in order to capture
- # `value == UNSET` cases before `type_cast_value()` gets called.
- #
- # Refs:
- # https://github.com/pallets/click/issues/3069
- if self.is_flag and not self.required and self.is_bool_flag and value is UNSET:
- value = False
-
- if self.callback is not None:
- value = self.callback(ctx, self, value)
-
- return value
-
- # in the normal case, rely on Parameter.process_value
- return super().process_value(ctx, value)
-
-
-class Argument(Parameter):
- """Arguments are positional parameters to a command. They generally
- provide fewer features than options but can have infinite ``nargs``
- and are required by default.
-
- All parameters are passed onwards to the constructor of :class:`Parameter`.
- """
-
- param_type_name = "argument"
-
- def __init__(
- self,
- param_decls: cabc.Sequence[str],
- required: bool | None = None,
- **attrs: t.Any,
- ) -> None:
- # Auto-detect the requirement status of the argument if not explicitly set.
- if required is None:
- # The argument gets automatically required if it has no explicit default
- # value set and is setup to match at least one value.
- if attrs.get("default", UNSET) is UNSET:
- required = attrs.get("nargs", 1) > 0
- # If the argument has a default value, it is not required.
- else:
- required = False
-
- if "multiple" in attrs:
- raise TypeError("__init__() got an unexpected keyword argument 'multiple'.")
-
- super().__init__(param_decls, required=required, **attrs)
-
- @property
- def human_readable_name(self) -> str:
- if self.metavar is not None:
- return self.metavar
- return self.name.upper() # type: ignore
-
- def make_metavar(self, ctx: Context) -> str:
- if self.metavar is not None:
- return self.metavar
- var = self.type.get_metavar(param=self, ctx=ctx)
- if not var:
- var = self.name.upper() # type: ignore
- if self.deprecated:
- var += "!"
- if not self.required:
- var = f"[{var}]"
- if self.nargs != 1:
- var += "..."
- return var
-
- def _parse_decls(
- self, decls: cabc.Sequence[str], expose_value: bool
- ) -> tuple[str | None, list[str], list[str]]:
- if not decls:
- if not expose_value:
- return None, [], []
- raise TypeError("Argument is marked as exposed, but does not have a name.")
- if len(decls) == 1:
- name = arg = decls[0]
- name = name.replace("-", "_").lower()
- else:
- raise TypeError(
- "Arguments take exactly one parameter declaration, got"
- f" {len(decls)}: {decls}."
- )
- return name, [arg], []
-
- def get_usage_pieces(self, ctx: Context) -> list[str]:
- return [self.make_metavar(ctx)]
-
- def get_error_hint(self, ctx: Context) -> str:
- return f"'{self.make_metavar(ctx)}'"
-
- def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None:
- parser.add_argument(dest=self.name, nargs=self.nargs, obj=self)
-
-
-def __getattr__(name: str) -> object:
- import warnings
-
- if name == "BaseCommand":
- warnings.warn(
- "'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
- " 'Command' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _BaseCommand
-
- if name == "MultiCommand":
- warnings.warn(
- "'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
- " 'Group' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _MultiCommand
-
- raise AttributeError(name)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/decorators.py b/backend/.venv/lib/python3.12/site-packages/click/decorators.py
deleted file mode 100644
index 21f4c34..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/decorators.py
+++ /dev/null
@@ -1,551 +0,0 @@
-from __future__ import annotations
-
-import inspect
-import typing as t
-from functools import update_wrapper
-from gettext import gettext as _
-
-from .core import Argument
-from .core import Command
-from .core import Context
-from .core import Group
-from .core import Option
-from .core import Parameter
-from .globals import get_current_context
-from .utils import echo
-
-if t.TYPE_CHECKING:
- import typing_extensions as te
-
- P = te.ParamSpec("P")
-
-R = t.TypeVar("R")
-T = t.TypeVar("T")
-_AnyCallable = t.Callable[..., t.Any]
-FC = t.TypeVar("FC", bound="_AnyCallable | Command")
-
-
-def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]:
- """Marks a callback as wanting to receive the current context
- object as first argument.
- """
-
- def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
- return f(get_current_context(), *args, **kwargs)
-
- return update_wrapper(new_func, f)
-
-
-def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
- """Similar to :func:`pass_context`, but only pass the object on the
- context onwards (:attr:`Context.obj`). This is useful if that object
- represents the state of a nested system.
- """
-
- def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
- return f(get_current_context().obj, *args, **kwargs)
-
- return update_wrapper(new_func, f)
-
-
-def make_pass_decorator(
- object_type: type[T], ensure: bool = False
-) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]:
- """Given an object type this creates a decorator that will work
- similar to :func:`pass_obj` but instead of passing the object of the
- current context, it will find the innermost context of type
- :func:`object_type`.
-
- This generates a decorator that works roughly like this::
-
- from functools import update_wrapper
-
- def decorator(f):
- @pass_context
- def new_func(ctx, *args, **kwargs):
- obj = ctx.find_object(object_type)
- return ctx.invoke(f, obj, *args, **kwargs)
- return update_wrapper(new_func, f)
- return decorator
-
- :param object_type: the type of the object to pass.
- :param ensure: if set to `True`, a new object will be created and
- remembered on the context if it's not there yet.
- """
-
- def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
- def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
- ctx = get_current_context()
-
- obj: T | None
- if ensure:
- obj = ctx.ensure_object(object_type)
- else:
- obj = ctx.find_object(object_type)
-
- if obj is None:
- raise RuntimeError(
- "Managed to invoke callback without a context"
- f" object of type {object_type.__name__!r}"
- " existing."
- )
-
- return ctx.invoke(f, obj, *args, **kwargs)
-
- return update_wrapper(new_func, f)
-
- return decorator
-
-
-def pass_meta_key(
- key: str, *, doc_description: str | None = None
-) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]:
- """Create a decorator that passes a key from
- :attr:`click.Context.meta` as the first argument to the decorated
- function.
-
- :param key: Key in ``Context.meta`` to pass.
- :param doc_description: Description of the object being passed,
- inserted into the decorator's docstring. Defaults to "the 'key'
- key from Context.meta".
-
- .. versionadded:: 8.0
- """
-
- def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
- def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
- ctx = get_current_context()
- obj = ctx.meta[key]
- return ctx.invoke(f, obj, *args, **kwargs)
-
- return update_wrapper(new_func, f)
-
- if doc_description is None:
- doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
-
- decorator.__doc__ = (
- f"Decorator that passes {doc_description} as the first argument"
- " to the decorated function."
- )
- return decorator
-
-
-CmdType = t.TypeVar("CmdType", bound=Command)
-
-
-# variant: no call, directly as decorator for a function.
-@t.overload
-def command(name: _AnyCallable) -> Command: ...
-
-
-# variant: with positional name and with positional or keyword cls argument:
-# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
-@t.overload
-def command(
- name: str | None,
- cls: type[CmdType],
- **attrs: t.Any,
-) -> t.Callable[[_AnyCallable], CmdType]: ...
-
-
-# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
-@t.overload
-def command(
- name: None = None,
- *,
- cls: type[CmdType],
- **attrs: t.Any,
-) -> t.Callable[[_AnyCallable], CmdType]: ...
-
-
-# variant: with optional string name, no cls argument provided.
-@t.overload
-def command(
- name: str | None = ..., cls: None = None, **attrs: t.Any
-) -> t.Callable[[_AnyCallable], Command]: ...
-
-
-def command(
- name: str | _AnyCallable | None = None,
- cls: type[CmdType] | None = None,
- **attrs: t.Any,
-) -> Command | t.Callable[[_AnyCallable], Command | CmdType]:
- r"""Creates a new :class:`Command` and uses the decorated function as
- callback. This will also automatically attach all decorated
- :func:`option`\s and :func:`argument`\s as parameters to the command.
-
- The name of the command defaults to the name of the function, converted to
- lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes
- ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example,
- ``init_data_command`` becomes ``init-data``.
-
- All keyword arguments are forwarded to the underlying command class.
- For the ``params`` argument, any decorated params are appended to
- the end of the list.
-
- Once decorated the function turns into a :class:`Command` instance
- that can be invoked as a command line utility or be attached to a
- command :class:`Group`.
-
- :param name: The name of the command. Defaults to modifying the function's
- name as described above.
- :param cls: The command class to create. Defaults to :class:`Command`.
-
- .. versionchanged:: 8.2
- The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are
- removed when generating the name.
-
- .. versionchanged:: 8.1
- This decorator can be applied without parentheses.
-
- .. versionchanged:: 8.1
- The ``params`` argument can be used. Decorated params are
- appended to the end of the list.
- """
-
- func: t.Callable[[_AnyCallable], t.Any] | None = None
-
- if callable(name):
- func = name
- name = None
- assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
- assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
-
- if cls is None:
- cls = t.cast("type[CmdType]", Command)
-
- def decorator(f: _AnyCallable) -> CmdType:
- if isinstance(f, Command):
- raise TypeError("Attempted to convert a callback into a command twice.")
-
- attr_params = attrs.pop("params", None)
- params = attr_params if attr_params is not None else []
-
- try:
- decorator_params = f.__click_params__ # type: ignore
- except AttributeError:
- pass
- else:
- del f.__click_params__ # type: ignore
- params.extend(reversed(decorator_params))
-
- if attrs.get("help") is None:
- attrs["help"] = f.__doc__
-
- if t.TYPE_CHECKING:
- assert cls is not None
- assert not callable(name)
-
- if name is not None:
- cmd_name = name
- else:
- cmd_name = f.__name__.lower().replace("_", "-")
- cmd_left, sep, suffix = cmd_name.rpartition("-")
-
- if sep and suffix in {"command", "cmd", "group", "grp"}:
- cmd_name = cmd_left
-
- cmd = cls(name=cmd_name, callback=f, params=params, **attrs)
- cmd.__doc__ = f.__doc__
- return cmd
-
- if func is not None:
- return decorator(func)
-
- return decorator
-
-
-GrpType = t.TypeVar("GrpType", bound=Group)
-
-
-# variant: no call, directly as decorator for a function.
-@t.overload
-def group(name: _AnyCallable) -> Group: ...
-
-
-# variant: with positional name and with positional or keyword cls argument:
-# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
-@t.overload
-def group(
- name: str | None,
- cls: type[GrpType],
- **attrs: t.Any,
-) -> t.Callable[[_AnyCallable], GrpType]: ...
-
-
-# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
-@t.overload
-def group(
- name: None = None,
- *,
- cls: type[GrpType],
- **attrs: t.Any,
-) -> t.Callable[[_AnyCallable], GrpType]: ...
-
-
-# variant: with optional string name, no cls argument provided.
-@t.overload
-def group(
- name: str | None = ..., cls: None = None, **attrs: t.Any
-) -> t.Callable[[_AnyCallable], Group]: ...
-
-
-def group(
- name: str | _AnyCallable | None = None,
- cls: type[GrpType] | None = None,
- **attrs: t.Any,
-) -> Group | t.Callable[[_AnyCallable], Group | GrpType]:
- """Creates a new :class:`Group` with a function as callback. This
- works otherwise the same as :func:`command` just that the `cls`
- parameter is set to :class:`Group`.
-
- .. versionchanged:: 8.1
- This decorator can be applied without parentheses.
- """
- if cls is None:
- cls = t.cast("type[GrpType]", Group)
-
- if callable(name):
- return command(cls=cls, **attrs)(name)
-
- return command(name, cls, **attrs)
-
-
-def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None:
- if isinstance(f, Command):
- f.params.append(param)
- else:
- if not hasattr(f, "__click_params__"):
- f.__click_params__ = [] # type: ignore
-
- f.__click_params__.append(param) # type: ignore
-
-
-def argument(
- *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any
-) -> t.Callable[[FC], FC]:
- """Attaches an argument to the command. All positional arguments are
- passed as parameter declarations to :class:`Argument`; all keyword
- arguments are forwarded unchanged (except ``cls``).
- This is equivalent to creating an :class:`Argument` instance manually
- and attaching it to the :attr:`Command.params` list.
-
- For the default argument class, refer to :class:`Argument` and
- :class:`Parameter` for descriptions of parameters.
-
- :param cls: the argument class to instantiate. This defaults to
- :class:`Argument`.
- :param param_decls: Passed as positional arguments to the constructor of
- ``cls``.
- :param attrs: Passed as keyword arguments to the constructor of ``cls``.
- """
- if cls is None:
- cls = Argument
-
- def decorator(f: FC) -> FC:
- _param_memo(f, cls(param_decls, **attrs))
- return f
-
- return decorator
-
-
-def option(
- *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any
-) -> t.Callable[[FC], FC]:
- """Attaches an option to the command. All positional arguments are
- passed as parameter declarations to :class:`Option`; all keyword
- arguments are forwarded unchanged (except ``cls``).
- This is equivalent to creating an :class:`Option` instance manually
- and attaching it to the :attr:`Command.params` list.
-
- For the default option class, refer to :class:`Option` and
- :class:`Parameter` for descriptions of parameters.
-
- :param cls: the option class to instantiate. This defaults to
- :class:`Option`.
- :param param_decls: Passed as positional arguments to the constructor of
- ``cls``.
- :param attrs: Passed as keyword arguments to the constructor of ``cls``.
- """
- if cls is None:
- cls = Option
-
- def decorator(f: FC) -> FC:
- _param_memo(f, cls(param_decls, **attrs))
- return f
-
- return decorator
-
-
-def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Add a ``--yes`` option which shows a prompt before continuing if
- not passed. If the prompt is declined, the program will exit.
-
- :param param_decls: One or more option names. Defaults to the single
- value ``"--yes"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
-
- def callback(ctx: Context, param: Parameter, value: bool) -> None:
- if not value:
- ctx.abort()
-
- if not param_decls:
- param_decls = ("--yes",)
-
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("callback", callback)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("prompt", "Do you want to continue?")
- kwargs.setdefault("help", "Confirm the action without prompting.")
- return option(*param_decls, **kwargs)
-
-
-def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Add a ``--password`` option which prompts for a password, hiding
- input and asking to enter the value again for confirmation.
-
- :param param_decls: One or more option names. Defaults to the single
- value ``"--password"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
- if not param_decls:
- param_decls = ("--password",)
-
- kwargs.setdefault("prompt", True)
- kwargs.setdefault("confirmation_prompt", True)
- kwargs.setdefault("hide_input", True)
- return option(*param_decls, **kwargs)
-
-
-def version_option(
- version: str | None = None,
- *param_decls: str,
- package_name: str | None = None,
- prog_name: str | None = None,
- message: str | None = None,
- **kwargs: t.Any,
-) -> t.Callable[[FC], FC]:
- """Add a ``--version`` option which immediately prints the version
- number and exits the program.
-
- If ``version`` is not provided, Click will try to detect it using
- :func:`importlib.metadata.version` to get the version for the
- ``package_name``.
-
- If ``package_name`` is not provided, Click will try to detect it by
- inspecting the stack frames. This will be used to detect the
- version, so it must match the name of the installed package.
-
- :param version: The version number to show. If not provided, Click
- will try to detect it.
- :param param_decls: One or more option names. Defaults to the single
- value ``"--version"``.
- :param package_name: The package name to detect the version from. If
- not provided, Click will try to detect it.
- :param prog_name: The name of the CLI to show in the message. If not
- provided, it will be detected from the command.
- :param message: The message to show. The values ``%(prog)s``,
- ``%(package)s``, and ``%(version)s`` are available. Defaults to
- ``"%(prog)s, version %(version)s"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- :raise RuntimeError: ``version`` could not be detected.
-
- .. versionchanged:: 8.0
- Add the ``package_name`` parameter, and the ``%(package)s``
- value for messages.
-
- .. versionchanged:: 8.0
- Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
- version is detected based on the package name, not the entry
- point name. The Python package name must match the installed
- package name, or be passed with ``package_name=``.
- """
- if message is None:
- message = _("%(prog)s, version %(version)s")
-
- if version is None and package_name is None:
- frame = inspect.currentframe()
- f_back = frame.f_back if frame is not None else None
- f_globals = f_back.f_globals if f_back is not None else None
- # break reference cycle
- # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
- del frame
-
- if f_globals is not None:
- package_name = f_globals.get("__name__")
-
- if package_name == "__main__":
- package_name = f_globals.get("__package__")
-
- if package_name:
- package_name = package_name.partition(".")[0]
-
- def callback(ctx: Context, param: Parameter, value: bool) -> None:
- if not value or ctx.resilient_parsing:
- return
-
- nonlocal prog_name
- nonlocal version
-
- if prog_name is None:
- prog_name = ctx.find_root().info_name
-
- if version is None and package_name is not None:
- import importlib.metadata
-
- try:
- version = importlib.metadata.version(package_name)
- except importlib.metadata.PackageNotFoundError:
- raise RuntimeError(
- f"{package_name!r} is not installed. Try passing"
- " 'package_name' instead."
- ) from None
-
- if version is None:
- raise RuntimeError(
- f"Could not determine the version for {package_name!r} automatically."
- )
-
- echo(
- message % {"prog": prog_name, "package": package_name, "version": version},
- color=ctx.color,
- )
- ctx.exit()
-
- if not param_decls:
- param_decls = ("--version",)
-
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("is_eager", True)
- kwargs.setdefault("help", _("Show the version and exit."))
- kwargs["callback"] = callback
- return option(*param_decls, **kwargs)
-
-
-def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Pre-configured ``--help`` option which immediately prints the help page
- and exits the program.
-
- :param param_decls: One or more option names. Defaults to the single
- value ``"--help"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
-
- def show_help(ctx: Context, param: Parameter, value: bool) -> None:
- """Callback that print the help page on ```` and exits."""
- if value and not ctx.resilient_parsing:
- echo(ctx.get_help(), color=ctx.color)
- ctx.exit()
-
- if not param_decls:
- param_decls = ("--help",)
-
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("is_eager", True)
- kwargs.setdefault("help", _("Show this message and exit."))
- kwargs.setdefault("callback", show_help)
-
- return option(*param_decls, **kwargs)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/exceptions.py b/backend/.venv/lib/python3.12/site-packages/click/exceptions.py
deleted file mode 100644
index 4d782ee..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/exceptions.py
+++ /dev/null
@@ -1,308 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import typing as t
-from gettext import gettext as _
-from gettext import ngettext
-
-from ._compat import get_text_stderr
-from .globals import resolve_color_default
-from .utils import echo
-from .utils import format_filename
-
-if t.TYPE_CHECKING:
- from .core import Command
- from .core import Context
- from .core import Parameter
-
-
-def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None:
- if param_hint is not None and not isinstance(param_hint, str):
- return " / ".join(repr(x) for x in param_hint)
-
- return param_hint
-
-
-class ClickException(Exception):
- """An exception that Click can handle and show to the user."""
-
- #: The exit code for this exception.
- exit_code = 1
-
- def __init__(self, message: str) -> None:
- super().__init__(message)
- # The context will be removed by the time we print the message, so cache
- # the color settings here to be used later on (in `show`)
- self.show_color: bool | None = resolve_color_default()
- self.message = message
-
- def format_message(self) -> str:
- return self.message
-
- def __str__(self) -> str:
- return self.message
-
- def show(self, file: t.IO[t.Any] | None = None) -> None:
- if file is None:
- file = get_text_stderr()
-
- echo(
- _("Error: {message}").format(message=self.format_message()),
- file=file,
- color=self.show_color,
- )
-
-
-class UsageError(ClickException):
- """An internal exception that signals a usage error. This typically
- aborts any further handling.
-
- :param message: the error message to display.
- :param ctx: optionally the context that caused this error. Click will
- fill in the context automatically in some situations.
- """
-
- exit_code = 2
-
- def __init__(self, message: str, ctx: Context | None = None) -> None:
- super().__init__(message)
- self.ctx = ctx
- self.cmd: Command | None = self.ctx.command if self.ctx else None
-
- def show(self, file: t.IO[t.Any] | None = None) -> None:
- if file is None:
- file = get_text_stderr()
- color = None
- hint = ""
- if (
- self.ctx is not None
- and self.ctx.command.get_help_option(self.ctx) is not None
- ):
- hint = _("Try '{command} {option}' for help.").format(
- command=self.ctx.command_path, option=self.ctx.help_option_names[0]
- )
- hint = f"{hint}\n"
- if self.ctx is not None:
- color = self.ctx.color
- echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
- echo(
- _("Error: {message}").format(message=self.format_message()),
- file=file,
- color=color,
- )
-
-
-class BadParameter(UsageError):
- """An exception that formats out a standardized error message for a
- bad parameter. This is useful when thrown from a callback or type as
- Click will attach contextual information to it (for instance, which
- parameter it is).
-
- .. versionadded:: 2.0
-
- :param param: the parameter object that caused this error. This can
- be left out, and Click will attach this info itself
- if possible.
- :param param_hint: a string that shows up as parameter name. This
- can be used as alternative to `param` in cases
- where custom validation should happen. If it is
- a string it's used as such, if it's a list then
- each item is quoted and separated.
- """
-
- def __init__(
- self,
- message: str,
- ctx: Context | None = None,
- param: Parameter | None = None,
- param_hint: cabc.Sequence[str] | str | None = None,
- ) -> None:
- super().__init__(message, ctx)
- self.param = param
- self.param_hint = param_hint
-
- def format_message(self) -> str:
- if self.param_hint is not None:
- param_hint = self.param_hint
- elif self.param is not None:
- param_hint = self.param.get_error_hint(self.ctx) # type: ignore
- else:
- return _("Invalid value: {message}").format(message=self.message)
-
- return _("Invalid value for {param_hint}: {message}").format(
- param_hint=_join_param_hints(param_hint), message=self.message
- )
-
-
-class MissingParameter(BadParameter):
- """Raised if click required an option or argument but it was not
- provided when invoking the script.
-
- .. versionadded:: 4.0
-
- :param param_type: a string that indicates the type of the parameter.
- The default is to inherit the parameter type from
- the given `param`. Valid values are ``'parameter'``,
- ``'option'`` or ``'argument'``.
- """
-
- def __init__(
- self,
- message: str | None = None,
- ctx: Context | None = None,
- param: Parameter | None = None,
- param_hint: cabc.Sequence[str] | str | None = None,
- param_type: str | None = None,
- ) -> None:
- super().__init__(message or "", ctx, param, param_hint)
- self.param_type = param_type
-
- def format_message(self) -> str:
- if self.param_hint is not None:
- param_hint: cabc.Sequence[str] | str | None = self.param_hint
- elif self.param is not None:
- param_hint = self.param.get_error_hint(self.ctx) # type: ignore
- else:
- param_hint = None
-
- param_hint = _join_param_hints(param_hint)
- param_hint = f" {param_hint}" if param_hint else ""
-
- param_type = self.param_type
- if param_type is None and self.param is not None:
- param_type = self.param.param_type_name
-
- msg = self.message
- if self.param is not None:
- msg_extra = self.param.type.get_missing_message(
- param=self.param, ctx=self.ctx
- )
- if msg_extra:
- if msg:
- msg += f". {msg_extra}"
- else:
- msg = msg_extra
-
- msg = f" {msg}" if msg else ""
-
- # Translate param_type for known types.
- if param_type == "argument":
- missing = _("Missing argument")
- elif param_type == "option":
- missing = _("Missing option")
- elif param_type == "parameter":
- missing = _("Missing parameter")
- else:
- missing = _("Missing {param_type}").format(param_type=param_type)
-
- return f"{missing}{param_hint}.{msg}"
-
- def __str__(self) -> str:
- if not self.message:
- param_name = self.param.name if self.param else None
- return _("Missing parameter: {param_name}").format(param_name=param_name)
- else:
- return self.message
-
-
-class NoSuchOption(UsageError):
- """Raised if click attempted to handle an option that does not
- exist.
-
- .. versionadded:: 4.0
- """
-
- def __init__(
- self,
- option_name: str,
- message: str | None = None,
- possibilities: cabc.Sequence[str] | None = None,
- ctx: Context | None = None,
- ) -> None:
- if message is None:
- message = _("No such option: {name}").format(name=option_name)
-
- super().__init__(message, ctx)
- self.option_name = option_name
- self.possibilities = possibilities
-
- def format_message(self) -> str:
- if not self.possibilities:
- return self.message
-
- possibility_str = ", ".join(sorted(self.possibilities))
- suggest = ngettext(
- "Did you mean {possibility}?",
- "(Possible options: {possibilities})",
- len(self.possibilities),
- ).format(possibility=possibility_str, possibilities=possibility_str)
- return f"{self.message} {suggest}"
-
-
-class BadOptionUsage(UsageError):
- """Raised if an option is generally supplied but the use of the option
- was incorrect. This is for instance raised if the number of arguments
- for an option is not correct.
-
- .. versionadded:: 4.0
-
- :param option_name: the name of the option being used incorrectly.
- """
-
- def __init__(
- self, option_name: str, message: str, ctx: Context | None = None
- ) -> None:
- super().__init__(message, ctx)
- self.option_name = option_name
-
-
-class BadArgumentUsage(UsageError):
- """Raised if an argument is generally supplied but the use of the argument
- was incorrect. This is for instance raised if the number of values
- for an argument is not correct.
-
- .. versionadded:: 6.0
- """
-
-
-class NoArgsIsHelpError(UsageError):
- def __init__(self, ctx: Context) -> None:
- self.ctx: Context
- super().__init__(ctx.get_help(), ctx=ctx)
-
- def show(self, file: t.IO[t.Any] | None = None) -> None:
- echo(self.format_message(), file=file, err=True, color=self.ctx.color)
-
-
-class FileError(ClickException):
- """Raised if a file cannot be opened."""
-
- def __init__(self, filename: str, hint: str | None = None) -> None:
- if hint is None:
- hint = _("unknown error")
-
- super().__init__(hint)
- self.ui_filename: str = format_filename(filename)
- self.filename = filename
-
- def format_message(self) -> str:
- return _("Could not open file {filename!r}: {message}").format(
- filename=self.ui_filename, message=self.message
- )
-
-
-class Abort(RuntimeError):
- """An internal signalling exception that signals Click to abort."""
-
-
-class Exit(RuntimeError):
- """An exception that indicates that the application should exit with some
- status code.
-
- :param code: the status code to exit with.
- """
-
- __slots__ = ("exit_code",)
-
- def __init__(self, code: int = 0) -> None:
- self.exit_code: int = code
diff --git a/backend/.venv/lib/python3.12/site-packages/click/formatting.py b/backend/.venv/lib/python3.12/site-packages/click/formatting.py
deleted file mode 100644
index 0b64f83..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/formatting.py
+++ /dev/null
@@ -1,301 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-from contextlib import contextmanager
-from gettext import gettext as _
-
-from ._compat import term_len
-from .parser import _split_opt
-
-# Can force a width. This is used by the test system
-FORCED_WIDTH: int | None = None
-
-
-def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]:
- widths: dict[int, int] = {}
-
- for row in rows:
- for idx, col in enumerate(row):
- widths[idx] = max(widths.get(idx, 0), term_len(col))
-
- return tuple(y for x, y in sorted(widths.items()))
-
-
-def iter_rows(
- rows: cabc.Iterable[tuple[str, str]], col_count: int
-) -> cabc.Iterator[tuple[str, ...]]:
- for row in rows:
- yield row + ("",) * (col_count - len(row))
-
-
-def wrap_text(
- text: str,
- width: int = 78,
- initial_indent: str = "",
- subsequent_indent: str = "",
- preserve_paragraphs: bool = False,
-) -> str:
- """A helper function that intelligently wraps text. By default, it
- assumes that it operates on a single paragraph of text but if the
- `preserve_paragraphs` parameter is provided it will intelligently
- handle paragraphs (defined by two empty lines).
-
- If paragraphs are handled, a paragraph can be prefixed with an empty
- line containing the ``\\b`` character (``\\x08``) to indicate that
- no rewrapping should happen in that block.
-
- :param text: the text that should be rewrapped.
- :param width: the maximum width for the text.
- :param initial_indent: the initial indent that should be placed on the
- first line as a string.
- :param subsequent_indent: the indent string that should be placed on
- each consecutive line.
- :param preserve_paragraphs: if this flag is set then the wrapping will
- intelligently handle paragraphs.
- """
- from ._textwrap import TextWrapper
-
- text = text.expandtabs()
- wrapper = TextWrapper(
- width,
- initial_indent=initial_indent,
- subsequent_indent=subsequent_indent,
- replace_whitespace=False,
- )
- if not preserve_paragraphs:
- return wrapper.fill(text)
-
- p: list[tuple[int, bool, str]] = []
- buf: list[str] = []
- indent = None
-
- def _flush_par() -> None:
- if not buf:
- return
- if buf[0].strip() == "\b":
- p.append((indent or 0, True, "\n".join(buf[1:])))
- else:
- p.append((indent or 0, False, " ".join(buf)))
- del buf[:]
-
- for line in text.splitlines():
- if not line:
- _flush_par()
- indent = None
- else:
- if indent is None:
- orig_len = term_len(line)
- line = line.lstrip()
- indent = orig_len - term_len(line)
- buf.append(line)
- _flush_par()
-
- rv = []
- for indent, raw, text in p:
- with wrapper.extra_indent(" " * indent):
- if raw:
- rv.append(wrapper.indent_only(text))
- else:
- rv.append(wrapper.fill(text))
-
- return "\n\n".join(rv)
-
-
-class HelpFormatter:
- """This class helps with formatting text-based help pages. It's
- usually just needed for very special internal cases, but it's also
- exposed so that developers can write their own fancy outputs.
-
- At present, it always writes into memory.
-
- :param indent_increment: the additional increment for each level.
- :param width: the width for the text. This defaults to the terminal
- width clamped to a maximum of 78.
- """
-
- def __init__(
- self,
- indent_increment: int = 2,
- width: int | None = None,
- max_width: int | None = None,
- ) -> None:
- self.indent_increment = indent_increment
- if max_width is None:
- max_width = 80
- if width is None:
- import shutil
-
- width = FORCED_WIDTH
- if width is None:
- width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
- self.width = width
- self.current_indent: int = 0
- self.buffer: list[str] = []
-
- def write(self, string: str) -> None:
- """Writes a unicode string into the internal buffer."""
- self.buffer.append(string)
-
- def indent(self) -> None:
- """Increases the indentation."""
- self.current_indent += self.indent_increment
-
- def dedent(self) -> None:
- """Decreases the indentation."""
- self.current_indent -= self.indent_increment
-
- def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None:
- """Writes a usage line into the buffer.
-
- :param prog: the program name.
- :param args: whitespace separated list of arguments.
- :param prefix: The prefix for the first line. Defaults to
- ``"Usage: "``.
- """
- if prefix is None:
- prefix = f"{_('Usage:')} "
-
- usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
- text_width = self.width - self.current_indent
-
- if text_width >= (term_len(usage_prefix) + 20):
- # The arguments will fit to the right of the prefix.
- indent = " " * term_len(usage_prefix)
- self.write(
- wrap_text(
- args,
- text_width,
- initial_indent=usage_prefix,
- subsequent_indent=indent,
- )
- )
- else:
- # The prefix is too long, put the arguments on the next line.
- self.write(usage_prefix)
- self.write("\n")
- indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
- self.write(
- wrap_text(
- args, text_width, initial_indent=indent, subsequent_indent=indent
- )
- )
-
- self.write("\n")
-
- def write_heading(self, heading: str) -> None:
- """Writes a heading into the buffer."""
- self.write(f"{'':>{self.current_indent}}{heading}:\n")
-
- def write_paragraph(self) -> None:
- """Writes a paragraph into the buffer."""
- if self.buffer:
- self.write("\n")
-
- def write_text(self, text: str) -> None:
- """Writes re-indented text into the buffer. This rewraps and
- preserves paragraphs.
- """
- indent = " " * self.current_indent
- self.write(
- wrap_text(
- text,
- self.width,
- initial_indent=indent,
- subsequent_indent=indent,
- preserve_paragraphs=True,
- )
- )
- self.write("\n")
-
- def write_dl(
- self,
- rows: cabc.Sequence[tuple[str, str]],
- col_max: int = 30,
- col_spacing: int = 2,
- ) -> None:
- """Writes a definition list into the buffer. This is how options
- and commands are usually formatted.
-
- :param rows: a list of two item tuples for the terms and values.
- :param col_max: the maximum width of the first column.
- :param col_spacing: the number of spaces between the first and
- second column.
- """
- rows = list(rows)
- widths = measure_table(rows)
- if len(widths) != 2:
- raise TypeError("Expected two columns for definition list")
-
- first_col = min(widths[0], col_max) + col_spacing
-
- for first, second in iter_rows(rows, len(widths)):
- self.write(f"{'':>{self.current_indent}}{first}")
- if not second:
- self.write("\n")
- continue
- if term_len(first) <= first_col - col_spacing:
- self.write(" " * (first_col - term_len(first)))
- else:
- self.write("\n")
- self.write(" " * (first_col + self.current_indent))
-
- text_width = max(self.width - first_col - 2, 10)
- wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
- lines = wrapped_text.splitlines()
-
- if lines:
- self.write(f"{lines[0]}\n")
-
- for line in lines[1:]:
- self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
- else:
- self.write("\n")
-
- @contextmanager
- def section(self, name: str) -> cabc.Iterator[None]:
- """Helpful context manager that writes a paragraph, a heading,
- and the indents.
-
- :param name: the section name that is written as heading.
- """
- self.write_paragraph()
- self.write_heading(name)
- self.indent()
- try:
- yield
- finally:
- self.dedent()
-
- @contextmanager
- def indentation(self) -> cabc.Iterator[None]:
- """A context manager that increases the indentation."""
- self.indent()
- try:
- yield
- finally:
- self.dedent()
-
- def getvalue(self) -> str:
- """Returns the buffer contents."""
- return "".join(self.buffer)
-
-
-def join_options(options: cabc.Sequence[str]) -> tuple[str, bool]:
- """Given a list of option strings this joins them in the most appropriate
- way and returns them in the form ``(formatted_string,
- any_prefix_is_slash)`` where the second item in the tuple is a flag that
- indicates if any of the option prefixes was a slash.
- """
- rv = []
- any_prefix_is_slash = False
-
- for opt in options:
- prefix = _split_opt(opt)[0]
-
- if prefix == "/":
- any_prefix_is_slash = True
-
- rv.append((len(prefix), opt))
-
- rv.sort(key=lambda x: x[0])
- return ", ".join(x[1] for x in rv), any_prefix_is_slash
diff --git a/backend/.venv/lib/python3.12/site-packages/click/globals.py b/backend/.venv/lib/python3.12/site-packages/click/globals.py
deleted file mode 100644
index a2f9172..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/globals.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from __future__ import annotations
-
-import typing as t
-from threading import local
-
-if t.TYPE_CHECKING:
- from .core import Context
-
-_local = local()
-
-
-@t.overload
-def get_current_context(silent: t.Literal[False] = False) -> Context: ...
-
-
-@t.overload
-def get_current_context(silent: bool = ...) -> Context | None: ...
-
-
-def get_current_context(silent: bool = False) -> Context | None:
- """Returns the current click context. This can be used as a way to
- access the current context object from anywhere. This is a more implicit
- alternative to the :func:`pass_context` decorator. This function is
- primarily useful for helpers such as :func:`echo` which might be
- interested in changing its behavior based on the current context.
-
- To push the current context, :meth:`Context.scope` can be used.
-
- .. versionadded:: 5.0
-
- :param silent: if set to `True` the return value is `None` if no context
- is available. The default behavior is to raise a
- :exc:`RuntimeError`.
- """
- try:
- return t.cast("Context", _local.stack[-1])
- except (AttributeError, IndexError) as e:
- if not silent:
- raise RuntimeError("There is no active click context.") from e
-
- return None
-
-
-def push_context(ctx: Context) -> None:
- """Pushes a new context to the current stack."""
- _local.__dict__.setdefault("stack", []).append(ctx)
-
-
-def pop_context() -> None:
- """Removes the top level from the stack."""
- _local.stack.pop()
-
-
-def resolve_color_default(color: bool | None = None) -> bool | None:
- """Internal helper to get the default value of the color flag. If a
- value is passed it's returned unchanged, otherwise it's looked up from
- the current context.
- """
- if color is not None:
- return color
-
- ctx = get_current_context(silent=True)
-
- if ctx is not None:
- return ctx.color
-
- return None
diff --git a/backend/.venv/lib/python3.12/site-packages/click/parser.py b/backend/.venv/lib/python3.12/site-packages/click/parser.py
deleted file mode 100644
index 1ea1f71..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/parser.py
+++ /dev/null
@@ -1,532 +0,0 @@
-"""
-This module started out as largely a copy paste from the stdlib's
-optparse module with the features removed that we do not need from
-optparse because we implement them in Click on a higher level (for
-instance type handling, help formatting and a lot more).
-
-The plan is to remove more and more from here over time.
-
-The reason this is a different module and not optparse from the stdlib
-is that there are differences in 2.x and 3.x about the error messages
-generated and optparse in the stdlib uses gettext for no good reason
-and might cause us issues.
-
-Click uses parts of optparse written by Gregory P. Ward and maintained
-by the Python Software Foundation. This is limited to code in parser.py.
-
-Copyright 2001-2006 Gregory P. Ward. All rights reserved.
-Copyright 2002-2006 Python Software Foundation. All rights reserved.
-"""
-
-# This code uses parts of optparse written by Gregory P. Ward and
-# maintained by the Python Software Foundation.
-# Copyright 2001-2006 Gregory P. Ward
-# Copyright 2002-2006 Python Software Foundation
-from __future__ import annotations
-
-import collections.abc as cabc
-import typing as t
-from collections import deque
-from gettext import gettext as _
-from gettext import ngettext
-
-from ._utils import FLAG_NEEDS_VALUE
-from ._utils import UNSET
-from .exceptions import BadArgumentUsage
-from .exceptions import BadOptionUsage
-from .exceptions import NoSuchOption
-from .exceptions import UsageError
-
-if t.TYPE_CHECKING:
- from ._utils import T_FLAG_NEEDS_VALUE
- from ._utils import T_UNSET
- from .core import Argument as CoreArgument
- from .core import Context
- from .core import Option as CoreOption
- from .core import Parameter as CoreParameter
-
-V = t.TypeVar("V")
-
-
-def _unpack_args(
- args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int]
-) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]:
- """Given an iterable of arguments and an iterable of nargs specifications,
- it returns a tuple with all the unpacked arguments at the first index
- and all remaining arguments as the second.
-
- The nargs specification is the number of arguments that should be consumed
- or `-1` to indicate that this position should eat up all the remainders.
-
- Missing items are filled with ``UNSET``.
- """
- args = deque(args)
- nargs_spec = deque(nargs_spec)
- rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = []
- spos: int | None = None
-
- def _fetch(c: deque[V]) -> V | T_UNSET:
- try:
- if spos is None:
- return c.popleft()
- else:
- return c.pop()
- except IndexError:
- return UNSET
-
- while nargs_spec:
- nargs = _fetch(nargs_spec)
-
- if nargs is None:
- continue
-
- if nargs == 1:
- rv.append(_fetch(args)) # type: ignore[arg-type]
- elif nargs > 1:
- x = [_fetch(args) for _ in range(nargs)]
-
- # If we're reversed, we're pulling in the arguments in reverse,
- # so we need to turn them around.
- if spos is not None:
- x.reverse()
-
- rv.append(tuple(x))
- elif nargs < 0:
- if spos is not None:
- raise TypeError("Cannot have two nargs < 0")
-
- spos = len(rv)
- rv.append(UNSET)
-
- # spos is the position of the wildcard (star). If it's not `None`,
- # we fill it with the remainder.
- if spos is not None:
- rv[spos] = tuple(args)
- args = []
- rv[spos + 1 :] = reversed(rv[spos + 1 :])
-
- return tuple(rv), list(args)
-
-
-def _split_opt(opt: str) -> tuple[str, str]:
- first = opt[:1]
- if first.isalnum():
- return "", opt
- if opt[1:2] == first:
- return opt[:2], opt[2:]
- return first, opt[1:]
-
-
-def _normalize_opt(opt: str, ctx: Context | None) -> str:
- if ctx is None or ctx.token_normalize_func is None:
- return opt
- prefix, opt = _split_opt(opt)
- return f"{prefix}{ctx.token_normalize_func(opt)}"
-
-
-class _Option:
- def __init__(
- self,
- obj: CoreOption,
- opts: cabc.Sequence[str],
- dest: str | None,
- action: str | None = None,
- nargs: int = 1,
- const: t.Any | None = None,
- ):
- self._short_opts = []
- self._long_opts = []
- self.prefixes: set[str] = set()
-
- for opt in opts:
- prefix, value = _split_opt(opt)
- if not prefix:
- raise ValueError(f"Invalid start character for option ({opt})")
- self.prefixes.add(prefix[0])
- if len(prefix) == 1 and len(value) == 1:
- self._short_opts.append(opt)
- else:
- self._long_opts.append(opt)
- self.prefixes.add(prefix)
-
- if action is None:
- action = "store"
-
- self.dest = dest
- self.action = action
- self.nargs = nargs
- self.const = const
- self.obj = obj
-
- @property
- def takes_value(self) -> bool:
- return self.action in ("store", "append")
-
- def process(self, value: t.Any, state: _ParsingState) -> None:
- if self.action == "store":
- state.opts[self.dest] = value # type: ignore
- elif self.action == "store_const":
- state.opts[self.dest] = self.const # type: ignore
- elif self.action == "append":
- state.opts.setdefault(self.dest, []).append(value) # type: ignore
- elif self.action == "append_const":
- state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
- elif self.action == "count":
- state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
- else:
- raise ValueError(f"unknown action '{self.action}'")
- state.order.append(self.obj)
-
-
-class _Argument:
- def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1):
- self.dest = dest
- self.nargs = nargs
- self.obj = obj
-
- def process(
- self,
- value: str | cabc.Sequence[str | None] | None | T_UNSET,
- state: _ParsingState,
- ) -> None:
- if self.nargs > 1:
- assert isinstance(value, cabc.Sequence)
- holes = sum(1 for x in value if x is UNSET)
- if holes == len(value):
- value = UNSET
- elif holes != 0:
- raise BadArgumentUsage(
- _("Argument {name!r} takes {nargs} values.").format(
- name=self.dest, nargs=self.nargs
- )
- )
-
- # We failed to collect any argument value so we consider the argument as unset.
- if value == ():
- value = UNSET
-
- state.opts[self.dest] = value # type: ignore
- state.order.append(self.obj)
-
-
-class _ParsingState:
- def __init__(self, rargs: list[str]) -> None:
- self.opts: dict[str, t.Any] = {}
- self.largs: list[str] = []
- self.rargs = rargs
- self.order: list[CoreParameter] = []
-
-
-class _OptionParser:
- """The option parser is an internal class that is ultimately used to
- parse options and arguments. It's modelled after optparse and brings
- a similar but vastly simplified API. It should generally not be used
- directly as the high level Click classes wrap it for you.
-
- It's not nearly as extensible as optparse or argparse as it does not
- implement features that are implemented on a higher level (such as
- types or defaults).
-
- :param ctx: optionally the :class:`~click.Context` where this parser
- should go with.
-
- .. deprecated:: 8.2
- Will be removed in Click 9.0.
- """
-
- def __init__(self, ctx: Context | None = None) -> None:
- #: The :class:`~click.Context` for this parser. This might be
- #: `None` for some advanced use cases.
- self.ctx = ctx
- #: This controls how the parser deals with interspersed arguments.
- #: If this is set to `False`, the parser will stop on the first
- #: non-option. Click uses this to implement nested subcommands
- #: safely.
- self.allow_interspersed_args: bool = True
- #: This tells the parser how to deal with unknown options. By
- #: default it will error out (which is sensible), but there is a
- #: second mode where it will ignore it and continue processing
- #: after shifting all the unknown options into the resulting args.
- self.ignore_unknown_options: bool = False
-
- if ctx is not None:
- self.allow_interspersed_args = ctx.allow_interspersed_args
- self.ignore_unknown_options = ctx.ignore_unknown_options
-
- self._short_opt: dict[str, _Option] = {}
- self._long_opt: dict[str, _Option] = {}
- self._opt_prefixes = {"-", "--"}
- self._args: list[_Argument] = []
-
- def add_option(
- self,
- obj: CoreOption,
- opts: cabc.Sequence[str],
- dest: str | None,
- action: str | None = None,
- nargs: int = 1,
- const: t.Any | None = None,
- ) -> None:
- """Adds a new option named `dest` to the parser. The destination
- is not inferred (unlike with optparse) and needs to be explicitly
- provided. Action can be any of ``store``, ``store_const``,
- ``append``, ``append_const`` or ``count``.
-
- The `obj` can be used to identify the option in the order list
- that is returned from the parser.
- """
- opts = [_normalize_opt(opt, self.ctx) for opt in opts]
- option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const)
- self._opt_prefixes.update(option.prefixes)
- for opt in option._short_opts:
- self._short_opt[opt] = option
- for opt in option._long_opts:
- self._long_opt[opt] = option
-
- def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:
- """Adds a positional argument named `dest` to the parser.
-
- The `obj` can be used to identify the option in the order list
- that is returned from the parser.
- """
- self._args.append(_Argument(obj, dest=dest, nargs=nargs))
-
- def parse_args(
- self, args: list[str]
- ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]:
- """Parses positional arguments and returns ``(values, args, order)``
- for the parsed options and arguments as well as the leftover
- arguments if there are any. The order is a list of objects as they
- appear on the command line. If arguments appear multiple times they
- will be memorized multiple times as well.
- """
- state = _ParsingState(args)
- try:
- self._process_args_for_options(state)
- self._process_args_for_args(state)
- except UsageError:
- if self.ctx is None or not self.ctx.resilient_parsing:
- raise
- return state.opts, state.largs, state.order
-
- def _process_args_for_args(self, state: _ParsingState) -> None:
- pargs, args = _unpack_args(
- state.largs + state.rargs, [x.nargs for x in self._args]
- )
-
- for idx, arg in enumerate(self._args):
- arg.process(pargs[idx], state)
-
- state.largs = args
- state.rargs = []
-
- def _process_args_for_options(self, state: _ParsingState) -> None:
- while state.rargs:
- arg = state.rargs.pop(0)
- arglen = len(arg)
- # Double dashes always handled explicitly regardless of what
- # prefixes are valid.
- if arg == "--":
- return
- elif arg[:1] in self._opt_prefixes and arglen > 1:
- self._process_opts(arg, state)
- elif self.allow_interspersed_args:
- state.largs.append(arg)
- else:
- state.rargs.insert(0, arg)
- return
-
- # Say this is the original argument list:
- # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
- # ^
- # (we are about to process arg(i)).
- #
- # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
- # [arg0, ..., arg(i-1)] (any options and their arguments will have
- # been removed from largs).
- #
- # The while loop will usually consume 1 or more arguments per pass.
- # If it consumes 1 (eg. arg is an option that takes no arguments),
- # then after _process_arg() is done the situation is:
- #
- # largs = subset of [arg0, ..., arg(i)]
- # rargs = [arg(i+1), ..., arg(N-1)]
- #
- # If allow_interspersed_args is false, largs will always be
- # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
- # not a very interesting subset!
-
- def _match_long_opt(
- self, opt: str, explicit_value: str | None, state: _ParsingState
- ) -> None:
- if opt not in self._long_opt:
- from difflib import get_close_matches
-
- possibilities = get_close_matches(opt, self._long_opt)
- raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
-
- option = self._long_opt[opt]
- if option.takes_value:
- # At this point it's safe to modify rargs by injecting the
- # explicit value, because no exception is raised in this
- # branch. This means that the inserted value will be fully
- # consumed.
- if explicit_value is not None:
- state.rargs.insert(0, explicit_value)
-
- value = self._get_value_from_state(opt, option, state)
-
- elif explicit_value is not None:
- raise BadOptionUsage(
- opt, _("Option {name!r} does not take a value.").format(name=opt)
- )
-
- else:
- value = UNSET
-
- option.process(value, state)
-
- def _match_short_opt(self, arg: str, state: _ParsingState) -> None:
- stop = False
- i = 1
- prefix = arg[0]
- unknown_options = []
-
- for ch in arg[1:]:
- opt = _normalize_opt(f"{prefix}{ch}", self.ctx)
- option = self._short_opt.get(opt)
- i += 1
-
- if not option:
- if self.ignore_unknown_options:
- unknown_options.append(ch)
- continue
- raise NoSuchOption(opt, ctx=self.ctx)
- if option.takes_value:
- # Any characters left in arg? Pretend they're the
- # next arg, and stop consuming characters of arg.
- if i < len(arg):
- state.rargs.insert(0, arg[i:])
- stop = True
-
- value = self._get_value_from_state(opt, option, state)
-
- else:
- value = UNSET
-
- option.process(value, state)
-
- if stop:
- break
-
- # If we got any unknown options we recombine the string of the
- # remaining options and re-attach the prefix, then report that
- # to the state as new larg. This way there is basic combinatorics
- # that can be achieved while still ignoring unknown arguments.
- if self.ignore_unknown_options and unknown_options:
- state.largs.append(f"{prefix}{''.join(unknown_options)}")
-
- def _get_value_from_state(
- self, option_name: str, option: _Option, state: _ParsingState
- ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE:
- nargs = option.nargs
-
- value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE
-
- if len(state.rargs) < nargs:
- if option.obj._flag_needs_value:
- # Option allows omitting the value.
- value = FLAG_NEEDS_VALUE
- else:
- raise BadOptionUsage(
- option_name,
- ngettext(
- "Option {name!r} requires an argument.",
- "Option {name!r} requires {nargs} arguments.",
- nargs,
- ).format(name=option_name, nargs=nargs),
- )
- elif nargs == 1:
- next_rarg = state.rargs[0]
-
- if (
- option.obj._flag_needs_value
- and isinstance(next_rarg, str)
- and next_rarg[:1] in self._opt_prefixes
- and len(next_rarg) > 1
- ):
- # The next arg looks like the start of an option, don't
- # use it as the value if omitting the value is allowed.
- value = FLAG_NEEDS_VALUE
- else:
- value = state.rargs.pop(0)
- else:
- value = tuple(state.rargs[:nargs])
- del state.rargs[:nargs]
-
- return value
-
- def _process_opts(self, arg: str, state: _ParsingState) -> None:
- explicit_value = None
- # Long option handling happens in two parts. The first part is
- # supporting explicitly attached values. In any case, we will try
- # to long match the option first.
- if "=" in arg:
- long_opt, explicit_value = arg.split("=", 1)
- else:
- long_opt = arg
- norm_long_opt = _normalize_opt(long_opt, self.ctx)
-
- # At this point we will match the (assumed) long option through
- # the long option matching code. Note that this allows options
- # like "-foo" to be matched as long options.
- try:
- self._match_long_opt(norm_long_opt, explicit_value, state)
- except NoSuchOption:
- # At this point the long option matching failed, and we need
- # to try with short options. However there is a special rule
- # which says, that if we have a two character options prefix
- # (applies to "--foo" for instance), we do not dispatch to the
- # short option code and will instead raise the no option
- # error.
- if arg[:2] not in self._opt_prefixes:
- self._match_short_opt(arg, state)
- return
-
- if not self.ignore_unknown_options:
- raise
-
- state.largs.append(arg)
-
-
-def __getattr__(name: str) -> object:
- import warnings
-
- if name in {
- "OptionParser",
- "Argument",
- "Option",
- "split_opt",
- "normalize_opt",
- "ParsingState",
- }:
- warnings.warn(
- f"'parser.{name}' is deprecated and will be removed in Click 9.0."
- " The old parser is available in 'optparse'.",
- DeprecationWarning,
- stacklevel=2,
- )
- return globals()[f"_{name}"]
-
- if name == "split_arg_string":
- from .shell_completion import split_arg_string
-
- warnings.warn(
- "Importing 'parser.split_arg_string' is deprecated, it will only be"
- " available in 'shell_completion' in Click 9.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- return split_arg_string
-
- raise AttributeError(name)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/py.typed b/backend/.venv/lib/python3.12/site-packages/click/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/click/shell_completion.py b/backend/.venv/lib/python3.12/site-packages/click/shell_completion.py
deleted file mode 100644
index 8f1564c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/shell_completion.py
+++ /dev/null
@@ -1,667 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import os
-import re
-import typing as t
-from gettext import gettext as _
-
-from .core import Argument
-from .core import Command
-from .core import Context
-from .core import Group
-from .core import Option
-from .core import Parameter
-from .core import ParameterSource
-from .utils import echo
-
-
-def shell_complete(
- cli: Command,
- ctx_args: cabc.MutableMapping[str, t.Any],
- prog_name: str,
- complete_var: str,
- instruction: str,
-) -> int:
- """Perform shell completion for the given CLI program.
-
- :param cli: Command being called.
- :param ctx_args: Extra arguments to pass to
- ``cli.make_context``.
- :param prog_name: Name of the executable in the shell.
- :param complete_var: Name of the environment variable that holds
- the completion instruction.
- :param instruction: Value of ``complete_var`` with the completion
- instruction and shell, in the form ``instruction_shell``.
- :return: Status code to exit with.
- """
- shell, _, instruction = instruction.partition("_")
- comp_cls = get_completion_class(shell)
-
- if comp_cls is None:
- return 1
-
- comp = comp_cls(cli, ctx_args, prog_name, complete_var)
-
- if instruction == "source":
- echo(comp.source())
- return 0
-
- if instruction == "complete":
- echo(comp.complete())
- return 0
-
- return 1
-
-
-class CompletionItem:
- """Represents a completion value and metadata about the value. The
- default metadata is ``type`` to indicate special shell handling,
- and ``help`` if a shell supports showing a help string next to the
- value.
-
- Arbitrary parameters can be passed when creating the object, and
- accessed using ``item.attr``. If an attribute wasn't passed,
- accessing it returns ``None``.
-
- :param value: The completion suggestion.
- :param type: Tells the shell script to provide special completion
- support for the type. Click uses ``"dir"`` and ``"file"``.
- :param help: String shown next to the value if supported.
- :param kwargs: Arbitrary metadata. The built-in implementations
- don't use this, but custom type completions paired with custom
- shell support could use it.
- """
-
- __slots__ = ("value", "type", "help", "_info")
-
- def __init__(
- self,
- value: t.Any,
- type: str = "plain",
- help: str | None = None,
- **kwargs: t.Any,
- ) -> None:
- self.value: t.Any = value
- self.type: str = type
- self.help: str | None = help
- self._info = kwargs
-
- def __getattr__(self, name: str) -> t.Any:
- return self._info.get(name)
-
-
-# Only Bash >= 4.4 has the nosort option.
-_SOURCE_BASH = """\
-%(complete_func)s() {
- local IFS=$'\\n'
- local response
-
- response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
-%(complete_var)s=bash_complete $1)
-
- for completion in $response; do
- IFS=',' read type value <<< "$completion"
-
- if [[ $type == 'dir' ]]; then
- COMPREPLY=()
- compopt -o dirnames
- elif [[ $type == 'file' ]]; then
- COMPREPLY=()
- compopt -o default
- elif [[ $type == 'plain' ]]; then
- COMPREPLY+=($value)
- fi
- done
-
- return 0
-}
-
-%(complete_func)s_setup() {
- complete -o nosort -F %(complete_func)s %(prog_name)s
-}
-
-%(complete_func)s_setup;
-"""
-
-# See ZshComplete.format_completion below, and issue #2703, before
-# changing this script.
-#
-# (TL;DR: _describe is picky about the format, but this Zsh script snippet
-# is already widely deployed. So freeze this script, and use clever-ish
-# handling of colons in ZshComplet.format_completion.)
-_SOURCE_ZSH = """\
-#compdef %(prog_name)s
-
-%(complete_func)s() {
- local -a completions
- local -a completions_with_descriptions
- local -a response
- (( ! $+commands[%(prog_name)s] )) && return 1
-
- response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
-%(complete_var)s=zsh_complete %(prog_name)s)}")
-
- for type key descr in ${response}; do
- if [[ "$type" == "plain" ]]; then
- if [[ "$descr" == "_" ]]; then
- completions+=("$key")
- else
- completions_with_descriptions+=("$key":"$descr")
- fi
- elif [[ "$type" == "dir" ]]; then
- _path_files -/
- elif [[ "$type" == "file" ]]; then
- _path_files -f
- fi
- done
-
- if [ -n "$completions_with_descriptions" ]; then
- _describe -V unsorted completions_with_descriptions -U
- fi
-
- if [ -n "$completions" ]; then
- compadd -U -V unsorted -a completions
- fi
-}
-
-if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
- # autoload from fpath, call function directly
- %(complete_func)s "$@"
-else
- # eval/source/. command, register function for later
- compdef %(complete_func)s %(prog_name)s
-fi
-"""
-
-_SOURCE_FISH = """\
-function %(complete_func)s;
- set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
-COMP_CWORD=(commandline -t) %(prog_name)s);
-
- for completion in $response;
- set -l metadata (string split "," $completion);
-
- if test $metadata[1] = "dir";
- __fish_complete_directories $metadata[2];
- else if test $metadata[1] = "file";
- __fish_complete_path $metadata[2];
- else if test $metadata[1] = "plain";
- echo $metadata[2];
- end;
- end;
-end;
-
-complete --no-files --command %(prog_name)s --arguments \
-"(%(complete_func)s)";
-"""
-
-
-class ShellComplete:
- """Base class for providing shell completion support. A subclass for
- a given shell will override attributes and methods to implement the
- completion instructions (``source`` and ``complete``).
-
- :param cli: Command being called.
- :param prog_name: Name of the executable in the shell.
- :param complete_var: Name of the environment variable that holds
- the completion instruction.
-
- .. versionadded:: 8.0
- """
-
- name: t.ClassVar[str]
- """Name to register the shell as with :func:`add_completion_class`.
- This is used in completion instructions (``{name}_source`` and
- ``{name}_complete``).
- """
-
- source_template: t.ClassVar[str]
- """Completion script template formatted by :meth:`source`. This must
- be provided by subclasses.
- """
-
- def __init__(
- self,
- cli: Command,
- ctx_args: cabc.MutableMapping[str, t.Any],
- prog_name: str,
- complete_var: str,
- ) -> None:
- self.cli = cli
- self.ctx_args = ctx_args
- self.prog_name = prog_name
- self.complete_var = complete_var
-
- @property
- def func_name(self) -> str:
- """The name of the shell function defined by the completion
- script.
- """
- safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII)
- return f"_{safe_name}_completion"
-
- def source_vars(self) -> dict[str, t.Any]:
- """Vars for formatting :attr:`source_template`.
-
- By default this provides ``complete_func``, ``complete_var``,
- and ``prog_name``.
- """
- return {
- "complete_func": self.func_name,
- "complete_var": self.complete_var,
- "prog_name": self.prog_name,
- }
-
- def source(self) -> str:
- """Produce the shell script that defines the completion
- function. By default this ``%``-style formats
- :attr:`source_template` with the dict returned by
- :meth:`source_vars`.
- """
- return self.source_template % self.source_vars()
-
- def get_completion_args(self) -> tuple[list[str], str]:
- """Use the env vars defined by the shell script to return a
- tuple of ``args, incomplete``. This must be implemented by
- subclasses.
- """
- raise NotImplementedError
-
- def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]:
- """Determine the context and last complete command or parameter
- from the complete args. Call that object's ``shell_complete``
- method to get the completions for the incomplete value.
-
- :param args: List of complete args before the incomplete value.
- :param incomplete: Value being completed. May be empty.
- """
- ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
- obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
- return obj.shell_complete(ctx, incomplete)
-
- def format_completion(self, item: CompletionItem) -> str:
- """Format a completion item into the form recognized by the
- shell script. This must be implemented by subclasses.
-
- :param item: Completion item to format.
- """
- raise NotImplementedError
-
- def complete(self) -> str:
- """Produce the completion data to send back to the shell.
-
- By default this calls :meth:`get_completion_args`, gets the
- completions, then calls :meth:`format_completion` for each
- completion.
- """
- args, incomplete = self.get_completion_args()
- completions = self.get_completions(args, incomplete)
- out = [self.format_completion(item) for item in completions]
- return "\n".join(out)
-
-
-class BashComplete(ShellComplete):
- """Shell completion for Bash."""
-
- name = "bash"
- source_template = _SOURCE_BASH
-
- @staticmethod
- def _check_version() -> None:
- import shutil
- import subprocess
-
- bash_exe = shutil.which("bash")
-
- if bash_exe is None:
- match = None
- else:
- output = subprocess.run(
- [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'],
- stdout=subprocess.PIPE,
- )
- match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
-
- if match is not None:
- major, minor = match.groups()
-
- if major < "4" or major == "4" and minor < "4":
- echo(
- _(
- "Shell completion is not supported for Bash"
- " versions older than 4.4."
- ),
- err=True,
- )
- else:
- echo(
- _("Couldn't detect Bash version, shell completion is not supported."),
- err=True,
- )
-
- def source(self) -> str:
- self._check_version()
- return super().source()
-
- def get_completion_args(self) -> tuple[list[str], str]:
- cwords = split_arg_string(os.environ["COMP_WORDS"])
- cword = int(os.environ["COMP_CWORD"])
- args = cwords[1:cword]
-
- try:
- incomplete = cwords[cword]
- except IndexError:
- incomplete = ""
-
- return args, incomplete
-
- def format_completion(self, item: CompletionItem) -> str:
- return f"{item.type},{item.value}"
-
-
-class ZshComplete(ShellComplete):
- """Shell completion for Zsh."""
-
- name = "zsh"
- source_template = _SOURCE_ZSH
-
- def get_completion_args(self) -> tuple[list[str], str]:
- cwords = split_arg_string(os.environ["COMP_WORDS"])
- cword = int(os.environ["COMP_CWORD"])
- args = cwords[1:cword]
-
- try:
- incomplete = cwords[cword]
- except IndexError:
- incomplete = ""
-
- return args, incomplete
-
- def format_completion(self, item: CompletionItem) -> str:
- help_ = item.help or "_"
- # The zsh completion script uses `_describe` on items with help
- # texts (which splits the item help from the item value at the
- # first unescaped colon) and `compadd` on items without help
- # text (which uses the item value as-is and does not support
- # colon escaping). So escape colons in the item value if and
- # only if the item help is not the sentinel "_" value, as used
- # by the completion script.
- #
- # (The zsh completion script is potentially widely deployed, and
- # thus harder to fix than this method.)
- #
- # See issue #1812 and issue #2703 for further context.
- value = item.value.replace(":", r"\:") if help_ != "_" else item.value
- return f"{item.type}\n{value}\n{help_}"
-
-
-class FishComplete(ShellComplete):
- """Shell completion for Fish."""
-
- name = "fish"
- source_template = _SOURCE_FISH
-
- def get_completion_args(self) -> tuple[list[str], str]:
- cwords = split_arg_string(os.environ["COMP_WORDS"])
- incomplete = os.environ["COMP_CWORD"]
- if incomplete:
- incomplete = split_arg_string(incomplete)[0]
- args = cwords[1:]
-
- # Fish stores the partial word in both COMP_WORDS and
- # COMP_CWORD, remove it from complete args.
- if incomplete and args and args[-1] == incomplete:
- args.pop()
-
- return args, incomplete
-
- def format_completion(self, item: CompletionItem) -> str:
- if item.help:
- return f"{item.type},{item.value}\t{item.help}"
-
- return f"{item.type},{item.value}"
-
-
-ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]")
-
-
-_available_shells: dict[str, type[ShellComplete]] = {
- "bash": BashComplete,
- "fish": FishComplete,
- "zsh": ZshComplete,
-}
-
-
-def add_completion_class(
- cls: ShellCompleteType, name: str | None = None
-) -> ShellCompleteType:
- """Register a :class:`ShellComplete` subclass under the given name.
- The name will be provided by the completion instruction environment
- variable during completion.
-
- :param cls: The completion class that will handle completion for the
- shell.
- :param name: Name to register the class under. Defaults to the
- class's ``name`` attribute.
- """
- if name is None:
- name = cls.name
-
- _available_shells[name] = cls
-
- return cls
-
-
-def get_completion_class(shell: str) -> type[ShellComplete] | None:
- """Look up a registered :class:`ShellComplete` subclass by the name
- provided by the completion instruction environment variable. If the
- name isn't registered, returns ``None``.
-
- :param shell: Name the class is registered under.
- """
- return _available_shells.get(shell)
-
-
-def split_arg_string(string: str) -> list[str]:
- """Split an argument string as with :func:`shlex.split`, but don't
- fail if the string is incomplete. Ignores a missing closing quote or
- incomplete escape sequence and uses the partial token as-is.
-
- .. code-block:: python
-
- split_arg_string("example 'my file")
- ["example", "my file"]
-
- split_arg_string("example my\\")
- ["example", "my"]
-
- :param string: String to split.
-
- .. versionchanged:: 8.2
- Moved to ``shell_completion`` from ``parser``.
- """
- import shlex
-
- lex = shlex.shlex(string, posix=True)
- lex.whitespace_split = True
- lex.commenters = ""
- out = []
-
- try:
- for token in lex:
- out.append(token)
- except ValueError:
- # Raised when end-of-string is reached in an invalid state. Use
- # the partial token as-is. The quote or escape character is in
- # lex.state, not lex.token.
- out.append(lex.token)
-
- return out
-
-
-def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
- """Determine if the given parameter is an argument that can still
- accept values.
-
- :param ctx: Invocation context for the command represented by the
- parsed complete args.
- :param param: Argument object being checked.
- """
- if not isinstance(param, Argument):
- return False
-
- assert param.name is not None
- # Will be None if expose_value is False.
- value = ctx.params.get(param.name)
- return (
- param.nargs == -1
- or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
- or (
- param.nargs > 1
- and isinstance(value, (tuple, list))
- and len(value) < param.nargs
- )
- )
-
-
-def _start_of_option(ctx: Context, value: str) -> bool:
- """Check if the value looks like the start of an option."""
- if not value:
- return False
-
- c = value[0]
- return c in ctx._opt_prefixes
-
-
-def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool:
- """Determine if the given parameter is an option that needs a value.
-
- :param args: List of complete args before the incomplete value.
- :param param: Option object being checked.
- """
- if not isinstance(param, Option):
- return False
-
- if param.is_flag or param.count:
- return False
-
- last_option = None
-
- for index, arg in enumerate(reversed(args)):
- if index + 1 > param.nargs:
- break
-
- if _start_of_option(ctx, arg):
- last_option = arg
- break
-
- return last_option is not None and last_option in param.opts
-
-
-def _resolve_context(
- cli: Command,
- ctx_args: cabc.MutableMapping[str, t.Any],
- prog_name: str,
- args: list[str],
-) -> Context:
- """Produce the context hierarchy starting with the command and
- traversing the complete arguments. This only follows the commands,
- it doesn't trigger input prompts or callbacks.
-
- :param cli: Command being called.
- :param prog_name: Name of the executable in the shell.
- :param args: List of complete args before the incomplete value.
- """
- ctx_args["resilient_parsing"] = True
- with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx:
- args = ctx._protected_args + ctx.args
-
- while args:
- command = ctx.command
-
- if isinstance(command, Group):
- if not command.chain:
- name, cmd, args = command.resolve_command(ctx, args)
-
- if cmd is None:
- return ctx
-
- with cmd.make_context(
- name, args, parent=ctx, resilient_parsing=True
- ) as sub_ctx:
- ctx = sub_ctx
- args = ctx._protected_args + ctx.args
- else:
- sub_ctx = ctx
-
- while args:
- name, cmd, args = command.resolve_command(ctx, args)
-
- if cmd is None:
- return ctx
-
- with cmd.make_context(
- name,
- args,
- parent=ctx,
- allow_extra_args=True,
- allow_interspersed_args=False,
- resilient_parsing=True,
- ) as sub_sub_ctx:
- sub_ctx = sub_sub_ctx
- args = sub_ctx.args
-
- ctx = sub_ctx
- args = [*sub_ctx._protected_args, *sub_ctx.args]
- else:
- break
-
- return ctx
-
-
-def _resolve_incomplete(
- ctx: Context, args: list[str], incomplete: str
-) -> tuple[Command | Parameter, str]:
- """Find the Click object that will handle the completion of the
- incomplete value. Return the object and the incomplete value.
-
- :param ctx: Invocation context for the command represented by
- the parsed complete args.
- :param args: List of complete args before the incomplete value.
- :param incomplete: Value being completed. May be empty.
- """
- # Different shells treat an "=" between a long option name and
- # value differently. Might keep the value joined, return the "="
- # as a separate item, or return the split name and value. Always
- # split and discard the "=" to make completion easier.
- if incomplete == "=":
- incomplete = ""
- elif "=" in incomplete and _start_of_option(ctx, incomplete):
- name, _, incomplete = incomplete.partition("=")
- args.append(name)
-
- # The "--" marker tells Click to stop treating values as options
- # even if they start with the option character. If it hasn't been
- # given and the incomplete arg looks like an option, the current
- # command will provide option name completions.
- if "--" not in args and _start_of_option(ctx, incomplete):
- return ctx.command, incomplete
-
- params = ctx.command.get_params(ctx)
-
- # If the last complete arg is an option name with an incomplete
- # value, the option will provide value completions.
- for param in params:
- if _is_incomplete_option(ctx, args, param):
- return param, incomplete
-
- # It's not an option name or value. The first argument without a
- # parsed value will provide value completions.
- for param in params:
- if _is_incomplete_argument(ctx, param):
- return param, incomplete
-
- # There were no unparsed arguments, the command may be a group that
- # will provide command name completions.
- return ctx.command, incomplete
diff --git a/backend/.venv/lib/python3.12/site-packages/click/termui.py b/backend/.venv/lib/python3.12/site-packages/click/termui.py
deleted file mode 100644
index 2e98a07..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/termui.py
+++ /dev/null
@@ -1,883 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import inspect
-import io
-import itertools
-import sys
-import typing as t
-from contextlib import AbstractContextManager
-from gettext import gettext as _
-
-from ._compat import isatty
-from ._compat import strip_ansi
-from .exceptions import Abort
-from .exceptions import UsageError
-from .globals import resolve_color_default
-from .types import Choice
-from .types import convert_type
-from .types import ParamType
-from .utils import echo
-from .utils import LazyFile
-
-if t.TYPE_CHECKING:
- from ._termui_impl import ProgressBar
-
-V = t.TypeVar("V")
-
-# The prompt functions to use. The doc tools currently override these
-# functions to customize how they work.
-visible_prompt_func: t.Callable[[str], str] = input
-
-_ansi_colors = {
- "black": 30,
- "red": 31,
- "green": 32,
- "yellow": 33,
- "blue": 34,
- "magenta": 35,
- "cyan": 36,
- "white": 37,
- "reset": 39,
- "bright_black": 90,
- "bright_red": 91,
- "bright_green": 92,
- "bright_yellow": 93,
- "bright_blue": 94,
- "bright_magenta": 95,
- "bright_cyan": 96,
- "bright_white": 97,
-}
-_ansi_reset_all = "\033[0m"
-
-
-def hidden_prompt_func(prompt: str) -> str:
- import getpass
-
- return getpass.getpass(prompt)
-
-
-def _build_prompt(
- text: str,
- suffix: str,
- show_default: bool = False,
- default: t.Any | None = None,
- show_choices: bool = True,
- type: ParamType | None = None,
-) -> str:
- prompt = text
- if type is not None and show_choices and isinstance(type, Choice):
- prompt += f" ({', '.join(map(str, type.choices))})"
- if default is not None and show_default:
- prompt = f"{prompt} [{_format_default(default)}]"
- return f"{prompt}{suffix}"
-
-
-def _format_default(default: t.Any) -> t.Any:
- if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
- return default.name
-
- return default
-
-
-def prompt(
- text: str,
- default: t.Any | None = None,
- hide_input: bool = False,
- confirmation_prompt: bool | str = False,
- type: ParamType | t.Any | None = None,
- value_proc: t.Callable[[str], t.Any] | None = None,
- prompt_suffix: str = ": ",
- show_default: bool = True,
- err: bool = False,
- show_choices: bool = True,
-) -> t.Any:
- """Prompts a user for input. This is a convenience function that can
- be used to prompt a user for input later.
-
- If the user aborts the input by sending an interrupt signal, this
- function will catch it and raise a :exc:`Abort` exception.
-
- :param text: the text to show for the prompt.
- :param default: the default value to use if no input happens. If this
- is not given it will prompt until it's aborted.
- :param hide_input: if this is set to true then the input value will
- be hidden.
- :param confirmation_prompt: Prompt a second time to confirm the
- value. Can be set to a string instead of ``True`` to customize
- the message.
- :param type: the type to use to check the value against.
- :param value_proc: if this parameter is provided it's a function that
- is invoked instead of the type conversion to
- convert a value.
- :param prompt_suffix: a suffix that should be added to the prompt.
- :param show_default: shows or hides the default value in the prompt.
- :param err: if set to true the file defaults to ``stderr`` instead of
- ``stdout``, the same as with echo.
- :param show_choices: Show or hide choices if the passed type is a Choice.
- For example if type is a Choice of either day or week,
- show_choices is true and text is "Group by" then the
- prompt will be "Group by (day, week): ".
-
- .. versionchanged:: 8.3.1
- A space is no longer appended to the prompt.
-
- .. versionadded:: 8.0
- ``confirmation_prompt`` can be a custom string.
-
- .. versionadded:: 7.0
- Added the ``show_choices`` parameter.
-
- .. versionadded:: 6.0
- Added unicode support for cmd.exe on Windows.
-
- .. versionadded:: 4.0
- Added the `err` parameter.
-
- """
-
- def prompt_func(text: str) -> str:
- f = hidden_prompt_func if hide_input else visible_prompt_func
- try:
- # Write the prompt separately so that we get nice
- # coloring through colorama on Windows
- echo(text[:-1], nl=False, err=err)
- # Echo the last character to stdout to work around an issue where
- # readline causes backspace to clear the whole line.
- return f(text[-1:])
- except (KeyboardInterrupt, EOFError):
- # getpass doesn't print a newline if the user aborts input with ^C.
- # Allegedly this behavior is inherited from getpass(3).
- # A doc bug has been filed at https://bugs.python.org/issue24711
- if hide_input:
- echo(None, err=err)
- raise Abort() from None
-
- if value_proc is None:
- value_proc = convert_type(type, default)
-
- prompt = _build_prompt(
- text, prompt_suffix, show_default, default, show_choices, type
- )
-
- if confirmation_prompt:
- if confirmation_prompt is True:
- confirmation_prompt = _("Repeat for confirmation")
-
- confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
-
- while True:
- while True:
- value = prompt_func(prompt)
- if value:
- break
- elif default is not None:
- value = default
- break
- try:
- result = value_proc(value)
- except UsageError as e:
- if hide_input:
- echo(_("Error: The value you entered was invalid."), err=err)
- else:
- echo(_("Error: {e.message}").format(e=e), err=err)
- continue
- if not confirmation_prompt:
- return result
- while True:
- value2 = prompt_func(confirmation_prompt)
- is_empty = not value and not value2
- if value2 or is_empty:
- break
- if value == value2:
- return result
- echo(_("Error: The two entered values do not match."), err=err)
-
-
-def confirm(
- text: str,
- default: bool | None = False,
- abort: bool = False,
- prompt_suffix: str = ": ",
- show_default: bool = True,
- err: bool = False,
-) -> bool:
- """Prompts for confirmation (yes/no question).
-
- If the user aborts the input by sending a interrupt signal this
- function will catch it and raise a :exc:`Abort` exception.
-
- :param text: the question to ask.
- :param default: The default value to use when no input is given. If
- ``None``, repeat until input is given.
- :param abort: if this is set to `True` a negative answer aborts the
- exception by raising :exc:`Abort`.
- :param prompt_suffix: a suffix that should be added to the prompt.
- :param show_default: shows or hides the default value in the prompt.
- :param err: if set to true the file defaults to ``stderr`` instead of
- ``stdout``, the same as with echo.
-
- .. versionchanged:: 8.3.1
- A space is no longer appended to the prompt.
-
- .. versionchanged:: 8.0
- Repeat until input is given if ``default`` is ``None``.
-
- .. versionadded:: 4.0
- Added the ``err`` parameter.
- """
- prompt = _build_prompt(
- text,
- prompt_suffix,
- show_default,
- "y/n" if default is None else ("Y/n" if default else "y/N"),
- )
-
- while True:
- try:
- # Write the prompt separately so that we get nice
- # coloring through colorama on Windows
- echo(prompt[:-1], nl=False, err=err)
- # Echo the last character to stdout to work around an issue where
- # readline causes backspace to clear the whole line.
- value = visible_prompt_func(prompt[-1:]).lower().strip()
- except (KeyboardInterrupt, EOFError):
- raise Abort() from None
- if value in ("y", "yes"):
- rv = True
- elif value in ("n", "no"):
- rv = False
- elif default is not None and value == "":
- rv = default
- else:
- echo(_("Error: invalid input"), err=err)
- continue
- break
- if abort and not rv:
- raise Abort()
- return rv
-
-
-def echo_via_pager(
- text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str,
- color: bool | None = None,
-) -> None:
- """This function takes a text and shows it via an environment specific
- pager on stdout.
-
- .. versionchanged:: 3.0
- Added the `color` flag.
-
- :param text_or_generator: the text to page, or alternatively, a
- generator emitting the text to page.
- :param color: controls if the pager supports ANSI colors or not. The
- default is autodetection.
- """
- color = resolve_color_default(color)
-
- if inspect.isgeneratorfunction(text_or_generator):
- i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)()
- elif isinstance(text_or_generator, str):
- i = [text_or_generator]
- else:
- i = iter(t.cast("cabc.Iterable[str]", text_or_generator))
-
- # convert every element of i to a text type if necessary
- text_generator = (el if isinstance(el, str) else str(el) for el in i)
-
- from ._termui_impl import pager
-
- return pager(itertools.chain(text_generator, "\n"), color)
-
-
-@t.overload
-def progressbar(
- *,
- length: int,
- label: str | None = None,
- hidden: bool = False,
- show_eta: bool = True,
- show_percent: bool | None = None,
- show_pos: bool = False,
- fill_char: str = "#",
- empty_char: str = "-",
- bar_template: str = "%(label)s [%(bar)s] %(info)s",
- info_sep: str = " ",
- width: int = 36,
- file: t.TextIO | None = None,
- color: bool | None = None,
- update_min_steps: int = 1,
-) -> ProgressBar[int]: ...
-
-
-@t.overload
-def progressbar(
- iterable: cabc.Iterable[V] | None = None,
- length: int | None = None,
- label: str | None = None,
- hidden: bool = False,
- show_eta: bool = True,
- show_percent: bool | None = None,
- show_pos: bool = False,
- item_show_func: t.Callable[[V | None], str | None] | None = None,
- fill_char: str = "#",
- empty_char: str = "-",
- bar_template: str = "%(label)s [%(bar)s] %(info)s",
- info_sep: str = " ",
- width: int = 36,
- file: t.TextIO | None = None,
- color: bool | None = None,
- update_min_steps: int = 1,
-) -> ProgressBar[V]: ...
-
-
-def progressbar(
- iterable: cabc.Iterable[V] | None = None,
- length: int | None = None,
- label: str | None = None,
- hidden: bool = False,
- show_eta: bool = True,
- show_percent: bool | None = None,
- show_pos: bool = False,
- item_show_func: t.Callable[[V | None], str | None] | None = None,
- fill_char: str = "#",
- empty_char: str = "-",
- bar_template: str = "%(label)s [%(bar)s] %(info)s",
- info_sep: str = " ",
- width: int = 36,
- file: t.TextIO | None = None,
- color: bool | None = None,
- update_min_steps: int = 1,
-) -> ProgressBar[V]:
- """This function creates an iterable context manager that can be used
- to iterate over something while showing a progress bar. It will
- either iterate over the `iterable` or `length` items (that are counted
- up). While iteration happens, this function will print a rendered
- progress bar to the given `file` (defaults to stdout) and will attempt
- to calculate remaining time and more. By default, this progress bar
- will not be rendered if the file is not a terminal.
-
- The context manager creates the progress bar. When the context
- manager is entered the progress bar is already created. With every
- iteration over the progress bar, the iterable passed to the bar is
- advanced and the bar is updated. When the context manager exits,
- a newline is printed and the progress bar is finalized on screen.
-
- Note: The progress bar is currently designed for use cases where the
- total progress can be expected to take at least several seconds.
- Because of this, the ProgressBar class object won't display
- progress that is considered too fast, and progress where the time
- between steps is less than a second.
-
- No printing must happen or the progress bar will be unintentionally
- destroyed.
-
- Example usage::
-
- with progressbar(items) as bar:
- for item in bar:
- do_something_with(item)
-
- Alternatively, if no iterable is specified, one can manually update the
- progress bar through the `update()` method instead of directly
- iterating over the progress bar. The update method accepts the number
- of steps to increment the bar with::
-
- with progressbar(length=chunks.total_bytes) as bar:
- for chunk in chunks:
- process_chunk(chunk)
- bar.update(chunks.bytes)
-
- The ``update()`` method also takes an optional value specifying the
- ``current_item`` at the new position. This is useful when used
- together with ``item_show_func`` to customize the output for each
- manual step::
-
- with click.progressbar(
- length=total_size,
- label='Unzipping archive',
- item_show_func=lambda a: a.filename
- ) as bar:
- for archive in zip_file:
- archive.extract()
- bar.update(archive.size, archive)
-
- :param iterable: an iterable to iterate over. If not provided the length
- is required.
- :param length: the number of items to iterate over. By default the
- progressbar will attempt to ask the iterator about its
- length, which might or might not work. If an iterable is
- also provided this parameter can be used to override the
- length. If an iterable is not provided the progress bar
- will iterate over a range of that length.
- :param label: the label to show next to the progress bar.
- :param hidden: hide the progressbar. Defaults to ``False``. When no tty is
- detected, it will only print the progressbar label. Setting this to
- ``False`` also disables that.
- :param show_eta: enables or disables the estimated time display. This is
- automatically disabled if the length cannot be
- determined.
- :param show_percent: enables or disables the percentage display. The
- default is `True` if the iterable has a length or
- `False` if not.
- :param show_pos: enables or disables the absolute position display. The
- default is `False`.
- :param item_show_func: A function called with the current item which
- can return a string to show next to the progress bar. If the
- function returns ``None`` nothing is shown. The current item can
- be ``None``, such as when entering and exiting the bar.
- :param fill_char: the character to use to show the filled part of the
- progress bar.
- :param empty_char: the character to use to show the non-filled part of
- the progress bar.
- :param bar_template: the format string to use as template for the bar.
- The parameters in it are ``label`` for the label,
- ``bar`` for the progress bar and ``info`` for the
- info section.
- :param info_sep: the separator between multiple info items (eta etc.)
- :param width: the width of the progress bar in characters, 0 means full
- terminal width
- :param file: The file to write to. If this is not a terminal then
- only the label is printed.
- :param color: controls if the terminal supports ANSI colors or not. The
- default is autodetection. This is only needed if ANSI
- codes are included anywhere in the progress bar output
- which is not the case by default.
- :param update_min_steps: Render only when this many updates have
- completed. This allows tuning for very fast iterators.
-
- .. versionadded:: 8.2
- The ``hidden`` argument.
-
- .. versionchanged:: 8.0
- Output is shown even if execution time is less than 0.5 seconds.
-
- .. versionchanged:: 8.0
- ``item_show_func`` shows the current item, not the previous one.
-
- .. versionchanged:: 8.0
- Labels are echoed if the output is not a TTY. Reverts a change
- in 7.0 that removed all output.
-
- .. versionadded:: 8.0
- The ``update_min_steps`` parameter.
-
- .. versionadded:: 4.0
- The ``color`` parameter and ``update`` method.
-
- .. versionadded:: 2.0
- """
- from ._termui_impl import ProgressBar
-
- color = resolve_color_default(color)
- return ProgressBar(
- iterable=iterable,
- length=length,
- hidden=hidden,
- show_eta=show_eta,
- show_percent=show_percent,
- show_pos=show_pos,
- item_show_func=item_show_func,
- fill_char=fill_char,
- empty_char=empty_char,
- bar_template=bar_template,
- info_sep=info_sep,
- file=file,
- label=label,
- width=width,
- color=color,
- update_min_steps=update_min_steps,
- )
-
-
-def clear() -> None:
- """Clears the terminal screen. This will have the effect of clearing
- the whole visible space of the terminal and moving the cursor to the
- top left. This does not do anything if not connected to a terminal.
-
- .. versionadded:: 2.0
- """
- if not isatty(sys.stdout):
- return
-
- # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor
- echo("\033[2J\033[1;1H", nl=False)
-
-
-def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str:
- if isinstance(color, int):
- return f"{38 + offset};5;{color:d}"
-
- if isinstance(color, (tuple, list)):
- r, g, b = color
- return f"{38 + offset};2;{r:d};{g:d};{b:d}"
-
- return str(_ansi_colors[color] + offset)
-
-
-def style(
- text: t.Any,
- fg: int | tuple[int, int, int] | str | None = None,
- bg: int | tuple[int, int, int] | str | None = None,
- bold: bool | None = None,
- dim: bool | None = None,
- underline: bool | None = None,
- overline: bool | None = None,
- italic: bool | None = None,
- blink: bool | None = None,
- reverse: bool | None = None,
- strikethrough: bool | None = None,
- reset: bool = True,
-) -> str:
- """Styles a text with ANSI styles and returns the new string. By
- default the styling is self contained which means that at the end
- of the string a reset code is issued. This can be prevented by
- passing ``reset=False``.
-
- Examples::
-
- click.echo(click.style('Hello World!', fg='green'))
- click.echo(click.style('ATTENTION!', blink=True))
- click.echo(click.style('Some things', reverse=True, fg='cyan'))
- click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
-
- Supported color names:
-
- * ``black`` (might be a gray)
- * ``red``
- * ``green``
- * ``yellow`` (might be an orange)
- * ``blue``
- * ``magenta``
- * ``cyan``
- * ``white`` (might be light gray)
- * ``bright_black``
- * ``bright_red``
- * ``bright_green``
- * ``bright_yellow``
- * ``bright_blue``
- * ``bright_magenta``
- * ``bright_cyan``
- * ``bright_white``
- * ``reset`` (reset the color code only)
-
- If the terminal supports it, color may also be specified as:
-
- - An integer in the interval [0, 255]. The terminal must support
- 8-bit/256-color mode.
- - An RGB tuple of three integers in [0, 255]. The terminal must
- support 24-bit/true-color mode.
-
- See https://en.wikipedia.org/wiki/ANSI_color and
- https://gist.github.com/XVilka/8346728 for more information.
-
- :param text: the string to style with ansi codes.
- :param fg: if provided this will become the foreground color.
- :param bg: if provided this will become the background color.
- :param bold: if provided this will enable or disable bold mode.
- :param dim: if provided this will enable or disable dim mode. This is
- badly supported.
- :param underline: if provided this will enable or disable underline.
- :param overline: if provided this will enable or disable overline.
- :param italic: if provided this will enable or disable italic.
- :param blink: if provided this will enable or disable blinking.
- :param reverse: if provided this will enable or disable inverse
- rendering (foreground becomes background and the
- other way round).
- :param strikethrough: if provided this will enable or disable
- striking through text.
- :param reset: by default a reset-all code is added at the end of the
- string which means that styles do not carry over. This
- can be disabled to compose styles.
-
- .. versionchanged:: 8.0
- A non-string ``message`` is converted to a string.
-
- .. versionchanged:: 8.0
- Added support for 256 and RGB color codes.
-
- .. versionchanged:: 8.0
- Added the ``strikethrough``, ``italic``, and ``overline``
- parameters.
-
- .. versionchanged:: 7.0
- Added support for bright colors.
-
- .. versionadded:: 2.0
- """
- if not isinstance(text, str):
- text = str(text)
-
- bits = []
-
- if fg:
- try:
- bits.append(f"\033[{_interpret_color(fg)}m")
- except KeyError:
- raise TypeError(f"Unknown color {fg!r}") from None
-
- if bg:
- try:
- bits.append(f"\033[{_interpret_color(bg, 10)}m")
- except KeyError:
- raise TypeError(f"Unknown color {bg!r}") from None
-
- if bold is not None:
- bits.append(f"\033[{1 if bold else 22}m")
- if dim is not None:
- bits.append(f"\033[{2 if dim else 22}m")
- if underline is not None:
- bits.append(f"\033[{4 if underline else 24}m")
- if overline is not None:
- bits.append(f"\033[{53 if overline else 55}m")
- if italic is not None:
- bits.append(f"\033[{3 if italic else 23}m")
- if blink is not None:
- bits.append(f"\033[{5 if blink else 25}m")
- if reverse is not None:
- bits.append(f"\033[{7 if reverse else 27}m")
- if strikethrough is not None:
- bits.append(f"\033[{9 if strikethrough else 29}m")
- bits.append(text)
- if reset:
- bits.append(_ansi_reset_all)
- return "".join(bits)
-
-
-def unstyle(text: str) -> str:
- """Removes ANSI styling information from a string. Usually it's not
- necessary to use this function as Click's echo function will
- automatically remove styling if necessary.
-
- .. versionadded:: 2.0
-
- :param text: the text to remove style information from.
- """
- return strip_ansi(text)
-
-
-def secho(
- message: t.Any | None = None,
- file: t.IO[t.AnyStr] | None = None,
- nl: bool = True,
- err: bool = False,
- color: bool | None = None,
- **styles: t.Any,
-) -> None:
- """This function combines :func:`echo` and :func:`style` into one
- call. As such the following two calls are the same::
-
- click.secho('Hello World!', fg='green')
- click.echo(click.style('Hello World!', fg='green'))
-
- All keyword arguments are forwarded to the underlying functions
- depending on which one they go with.
-
- Non-string types will be converted to :class:`str`. However,
- :class:`bytes` are passed directly to :meth:`echo` without applying
- style. If you want to style bytes that represent text, call
- :meth:`bytes.decode` first.
-
- .. versionchanged:: 8.0
- A non-string ``message`` is converted to a string. Bytes are
- passed through without style applied.
-
- .. versionadded:: 2.0
- """
- if message is not None and not isinstance(message, (bytes, bytearray)):
- message = style(message, **styles)
-
- return echo(message, file=file, nl=nl, err=err, color=color)
-
-
-@t.overload
-def edit(
- text: bytes | bytearray,
- editor: str | None = None,
- env: cabc.Mapping[str, str] | None = None,
- require_save: bool = False,
- extension: str = ".txt",
-) -> bytes | None: ...
-
-
-@t.overload
-def edit(
- text: str,
- editor: str | None = None,
- env: cabc.Mapping[str, str] | None = None,
- require_save: bool = True,
- extension: str = ".txt",
-) -> str | None: ...
-
-
-@t.overload
-def edit(
- text: None = None,
- editor: str | None = None,
- env: cabc.Mapping[str, str] | None = None,
- require_save: bool = True,
- extension: str = ".txt",
- filename: str | cabc.Iterable[str] | None = None,
-) -> None: ...
-
-
-def edit(
- text: str | bytes | bytearray | None = None,
- editor: str | None = None,
- env: cabc.Mapping[str, str] | None = None,
- require_save: bool = True,
- extension: str = ".txt",
- filename: str | cabc.Iterable[str] | None = None,
-) -> str | bytes | bytearray | None:
- r"""Edits the given text in the defined editor. If an editor is given
- (should be the full path to the executable but the regular operating
- system search path is used for finding the executable) it overrides
- the detected editor. Optionally, some environment variables can be
- used. If the editor is closed without changes, `None` is returned. In
- case a file is edited directly the return value is always `None` and
- `require_save` and `extension` are ignored.
-
- If the editor cannot be opened a :exc:`UsageError` is raised.
-
- Note for Windows: to simplify cross-platform usage, the newlines are
- automatically converted from POSIX to Windows and vice versa. As such,
- the message here will have ``\n`` as newline markers.
-
- :param text: the text to edit.
- :param editor: optionally the editor to use. Defaults to automatic
- detection.
- :param env: environment variables to forward to the editor.
- :param require_save: if this is true, then not saving in the editor
- will make the return value become `None`.
- :param extension: the extension to tell the editor about. This defaults
- to `.txt` but changing this might change syntax
- highlighting.
- :param filename: if provided it will edit this file instead of the
- provided text contents. It will not use a temporary
- file as an indirection in that case. If the editor supports
- editing multiple files at once, a sequence of files may be
- passed as well. Invoke `click.file` once per file instead
- if multiple files cannot be managed at once or editing the
- files serially is desired.
-
- .. versionchanged:: 8.2.0
- ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str``
- if the ``editor`` supports editing multiple files at once.
-
- """
- from ._termui_impl import Editor
-
- ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension)
-
- if filename is None:
- return ed.edit(text)
-
- if isinstance(filename, str):
- filename = (filename,)
-
- ed.edit_files(filenames=filename)
- return None
-
-
-def launch(url: str, wait: bool = False, locate: bool = False) -> int:
- """This function launches the given URL (or filename) in the default
- viewer application for this file type. If this is an executable, it
- might launch the executable in a new session. The return value is
- the exit code of the launched application. Usually, ``0`` indicates
- success.
-
- Examples::
-
- click.launch('https://click.palletsprojects.com/')
- click.launch('/my/downloaded/file', locate=True)
-
- .. versionadded:: 2.0
-
- :param url: URL or filename of the thing to launch.
- :param wait: Wait for the program to exit before returning. This
- only works if the launched program blocks. In particular,
- ``xdg-open`` on Linux does not block.
- :param locate: if this is set to `True` then instead of launching the
- application associated with the URL it will attempt to
- launch a file manager with the file located. This
- might have weird effects if the URL does not point to
- the filesystem.
- """
- from ._termui_impl import open_url
-
- return open_url(url, wait=wait, locate=locate)
-
-
-# If this is provided, getchar() calls into this instead. This is used
-# for unittesting purposes.
-_getchar: t.Callable[[bool], str] | None = None
-
-
-def getchar(echo: bool = False) -> str:
- """Fetches a single character from the terminal and returns it. This
- will always return a unicode character and under certain rare
- circumstances this might return more than one character. The
- situations which more than one character is returned is when for
- whatever reason multiple characters end up in the terminal buffer or
- standard input was not actually a terminal.
-
- Note that this will always read from the terminal, even if something
- is piped into the standard input.
-
- Note for Windows: in rare cases when typing non-ASCII characters, this
- function might wait for a second character and then return both at once.
- This is because certain Unicode characters look like special-key markers.
-
- .. versionadded:: 2.0
-
- :param echo: if set to `True`, the character read will also show up on
- the terminal. The default is to not show it.
- """
- global _getchar
-
- if _getchar is None:
- from ._termui_impl import getchar as f
-
- _getchar = f
-
- return _getchar(echo)
-
-
-def raw_terminal() -> AbstractContextManager[int]:
- from ._termui_impl import raw_terminal as f
-
- return f()
-
-
-def pause(info: str | None = None, err: bool = False) -> None:
- """This command stops execution and waits for the user to press any
- key to continue. This is similar to the Windows batch "pause"
- command. If the program is not run through a terminal, this command
- will instead do nothing.
-
- .. versionadded:: 2.0
-
- .. versionadded:: 4.0
- Added the `err` parameter.
-
- :param info: The message to print before pausing. Defaults to
- ``"Press any key to continue..."``.
- :param err: if set to message goes to ``stderr`` instead of
- ``stdout``, the same as with echo.
- """
- if not isatty(sys.stdin) or not isatty(sys.stdout):
- return
-
- if info is None:
- info = _("Press any key to continue...")
-
- try:
- if info:
- echo(info, nl=False, err=err)
- try:
- getchar()
- except (KeyboardInterrupt, EOFError):
- pass
- finally:
- if info:
- echo(err=err)
diff --git a/backend/.venv/lib/python3.12/site-packages/click/testing.py b/backend/.venv/lib/python3.12/site-packages/click/testing.py
deleted file mode 100644
index f6f60b8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/testing.py
+++ /dev/null
@@ -1,577 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import contextlib
-import io
-import os
-import shlex
-import sys
-import tempfile
-import typing as t
-from types import TracebackType
-
-from . import _compat
-from . import formatting
-from . import termui
-from . import utils
-from ._compat import _find_binary_reader
-
-if t.TYPE_CHECKING:
- from _typeshed import ReadableBuffer
-
- from .core import Command
-
-
-class EchoingStdin:
- def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
- self._input = input
- self._output = output
- self._paused = False
-
- def __getattr__(self, x: str) -> t.Any:
- return getattr(self._input, x)
-
- def _echo(self, rv: bytes) -> bytes:
- if not self._paused:
- self._output.write(rv)
-
- return rv
-
- def read(self, n: int = -1) -> bytes:
- return self._echo(self._input.read(n))
-
- def read1(self, n: int = -1) -> bytes:
- return self._echo(self._input.read1(n)) # type: ignore
-
- def readline(self, n: int = -1) -> bytes:
- return self._echo(self._input.readline(n))
-
- def readlines(self) -> list[bytes]:
- return [self._echo(x) for x in self._input.readlines()]
-
- def __iter__(self) -> cabc.Iterator[bytes]:
- return iter(self._echo(x) for x in self._input)
-
- def __repr__(self) -> str:
- return repr(self._input)
-
-
-@contextlib.contextmanager
-def _pause_echo(stream: EchoingStdin | None) -> cabc.Iterator[None]:
- if stream is None:
- yield
- else:
- stream._paused = True
- yield
- stream._paused = False
-
-
-class BytesIOCopy(io.BytesIO):
- """Patch ``io.BytesIO`` to let the written stream be copied to another.
-
- .. versionadded:: 8.2
- """
-
- def __init__(self, copy_to: io.BytesIO) -> None:
- super().__init__()
- self.copy_to = copy_to
-
- def flush(self) -> None:
- super().flush()
- self.copy_to.flush()
-
- def write(self, b: ReadableBuffer) -> int:
- self.copy_to.write(b)
- return super().write(b)
-
-
-class StreamMixer:
- """Mixes `` and `` streams.
-
- The result is available in the ``output`` attribute.
-
- .. versionadded:: 8.2
- """
-
- def __init__(self) -> None:
- self.output: io.BytesIO = io.BytesIO()
- self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output)
- self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output)
-
- def __del__(self) -> None:
- """
- Guarantee that embedded file-like objects are closed in a
- predictable order, protecting against races between
- self.output being closed and other streams being flushed on close
-
- .. versionadded:: 8.2.2
- """
- self.stderr.close()
- self.stdout.close()
- self.output.close()
-
-
-class _NamedTextIOWrapper(io.TextIOWrapper):
- def __init__(
- self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
- ) -> None:
- super().__init__(buffer, **kwargs)
- self._name = name
- self._mode = mode
-
- @property
- def name(self) -> str:
- return self._name
-
- @property
- def mode(self) -> str:
- return self._mode
-
-
-def make_input_stream(
- input: str | bytes | t.IO[t.Any] | None, charset: str
-) -> t.BinaryIO:
- # Is already an input stream.
- if hasattr(input, "read"):
- rv = _find_binary_reader(t.cast("t.IO[t.Any]", input))
-
- if rv is not None:
- return rv
-
- raise TypeError("Could not find binary reader for input stream.")
-
- if input is None:
- input = b""
- elif isinstance(input, str):
- input = input.encode(charset)
-
- return io.BytesIO(input)
-
-
-class Result:
- """Holds the captured result of an invoked CLI script.
-
- :param runner: The runner that created the result
- :param stdout_bytes: The standard output as bytes.
- :param stderr_bytes: The standard error as bytes.
- :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the
- user would see it in its terminal.
- :param return_value: The value returned from the invoked command.
- :param exit_code: The exit code as integer.
- :param exception: The exception that happened if one did.
- :param exc_info: Exception information (exception type, exception instance,
- traceback type).
-
- .. versionchanged:: 8.2
- ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and
- ``mix_stderr`` has been removed.
-
- .. versionadded:: 8.0
- Added ``return_value``.
- """
-
- def __init__(
- self,
- runner: CliRunner,
- stdout_bytes: bytes,
- stderr_bytes: bytes,
- output_bytes: bytes,
- return_value: t.Any,
- exit_code: int,
- exception: BaseException | None,
- exc_info: tuple[type[BaseException], BaseException, TracebackType]
- | None = None,
- ):
- self.runner = runner
- self.stdout_bytes = stdout_bytes
- self.stderr_bytes = stderr_bytes
- self.output_bytes = output_bytes
- self.return_value = return_value
- self.exit_code = exit_code
- self.exception = exception
- self.exc_info = exc_info
-
- @property
- def output(self) -> str:
- """The terminal output as unicode string, as the user would see it.
-
- .. versionchanged:: 8.2
- No longer a proxy for ``self.stdout``. Now has its own independent stream
- that is mixing `` and ``, in the order they were written.
- """
- return self.output_bytes.decode(self.runner.charset, "replace").replace(
- "\r\n", "\n"
- )
-
- @property
- def stdout(self) -> str:
- """The standard output as unicode string."""
- return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
- "\r\n", "\n"
- )
-
- @property
- def stderr(self) -> str:
- """The standard error as unicode string.
-
- .. versionchanged:: 8.2
- No longer raise an exception, always returns the `` string.
- """
- return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
- "\r\n", "\n"
- )
-
- def __repr__(self) -> str:
- exc_str = repr(self.exception) if self.exception else "okay"
- return f"<{type(self).__name__} {exc_str}>"
-
-
-class CliRunner:
- """The CLI runner provides functionality to invoke a Click command line
- script for unittesting purposes in a isolated environment. This only
- works in single-threaded systems without any concurrency as it changes the
- global interpreter state.
-
- :param charset: the character set for the input and output data.
- :param env: a dictionary with environment variables for overriding.
- :param echo_stdin: if this is set to `True`, then reading from `` writes
- to ``. This is useful for showing examples in
- some circumstances. Note that regular prompts
- will automatically echo the input.
- :param catch_exceptions: Whether to catch any exceptions other than
- ``SystemExit`` when running :meth:`~CliRunner.invoke`.
-
- .. versionchanged:: 8.2
- Added the ``catch_exceptions`` parameter.
-
- .. versionchanged:: 8.2
- ``mix_stderr`` parameter has been removed.
- """
-
- def __init__(
- self,
- charset: str = "utf-8",
- env: cabc.Mapping[str, str | None] | None = None,
- echo_stdin: bool = False,
- catch_exceptions: bool = True,
- ) -> None:
- self.charset = charset
- self.env: cabc.Mapping[str, str | None] = env or {}
- self.echo_stdin = echo_stdin
- self.catch_exceptions = catch_exceptions
-
- def get_default_prog_name(self, cli: Command) -> str:
- """Given a command object it will return the default program name
- for it. The default is the `name` attribute or ``"root"`` if not
- set.
- """
- return cli.name or "root"
-
- def make_env(
- self, overrides: cabc.Mapping[str, str | None] | None = None
- ) -> cabc.Mapping[str, str | None]:
- """Returns the environment overrides for invoking a script."""
- rv = dict(self.env)
- if overrides:
- rv.update(overrides)
- return rv
-
- @contextlib.contextmanager
- def isolation(
- self,
- input: str | bytes | t.IO[t.Any] | None = None,
- env: cabc.Mapping[str, str | None] | None = None,
- color: bool = False,
- ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]:
- """A context manager that sets up the isolation for invoking of a
- command line tool. This sets up `` with the given input data
- and `os.environ` with the overrides from the given dictionary.
- This also rebinds some internals in Click to be mocked (like the
- prompt functionality).
-
- This is automatically done in the :meth:`invoke` method.
-
- :param input: the input stream to put into `sys.stdin`.
- :param env: the environment overrides as dictionary.
- :param color: whether the output should contain color codes. The
- application can still override this explicitly.
-
- .. versionadded:: 8.2
- An additional output stream is returned, which is a mix of
- `` and `` streams.
-
- .. versionchanged:: 8.2
- Always returns the `` stream.
-
- .. versionchanged:: 8.0
- `` is opened with ``errors="backslashreplace"``
- instead of the default ``"strict"``.
-
- .. versionchanged:: 4.0
- Added the ``color`` parameter.
- """
- bytes_input = make_input_stream(input, self.charset)
- echo_input = None
-
- old_stdin = sys.stdin
- old_stdout = sys.stdout
- old_stderr = sys.stderr
- old_forced_width = formatting.FORCED_WIDTH
- formatting.FORCED_WIDTH = 80
-
- env = self.make_env(env)
-
- stream_mixer = StreamMixer()
-
- if self.echo_stdin:
- bytes_input = echo_input = t.cast(
- t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout)
- )
-
- sys.stdin = text_input = _NamedTextIOWrapper(
- bytes_input, encoding=self.charset, name="", mode="r"
- )
-
- if self.echo_stdin:
- # Force unbuffered reads, otherwise TextIOWrapper reads a
- # large chunk which is echoed early.
- text_input._CHUNK_SIZE = 1 # type: ignore
-
- sys.stdout = _NamedTextIOWrapper(
- stream_mixer.stdout, encoding=self.charset, name="", mode="w"
- )
-
- sys.stderr = _NamedTextIOWrapper(
- stream_mixer.stderr,
- encoding=self.charset,
- name="",
- mode="w",
- errors="backslashreplace",
- )
-
- @_pause_echo(echo_input) # type: ignore
- def visible_input(prompt: str | None = None) -> str:
- sys.stdout.write(prompt or "")
- try:
- val = next(text_input).rstrip("\r\n")
- except StopIteration as e:
- raise EOFError() from e
- sys.stdout.write(f"{val}\n")
- sys.stdout.flush()
- return val
-
- @_pause_echo(echo_input) # type: ignore
- def hidden_input(prompt: str | None = None) -> str:
- sys.stdout.write(f"{prompt or ''}\n")
- sys.stdout.flush()
- try:
- return next(text_input).rstrip("\r\n")
- except StopIteration as e:
- raise EOFError() from e
-
- @_pause_echo(echo_input) # type: ignore
- def _getchar(echo: bool) -> str:
- char = sys.stdin.read(1)
-
- if echo:
- sys.stdout.write(char)
-
- sys.stdout.flush()
- return char
-
- default_color = color
-
- def should_strip_ansi(
- stream: t.IO[t.Any] | None = None, color: bool | None = None
- ) -> bool:
- if color is None:
- return not default_color
- return not color
-
- old_visible_prompt_func = termui.visible_prompt_func
- old_hidden_prompt_func = termui.hidden_prompt_func
- old__getchar_func = termui._getchar
- old_should_strip_ansi = utils.should_strip_ansi # type: ignore
- old__compat_should_strip_ansi = _compat.should_strip_ansi
- termui.visible_prompt_func = visible_input
- termui.hidden_prompt_func = hidden_input
- termui._getchar = _getchar
- utils.should_strip_ansi = should_strip_ansi # type: ignore
- _compat.should_strip_ansi = should_strip_ansi
-
- old_env = {}
- try:
- for key, value in env.items():
- old_env[key] = os.environ.get(key)
- if value is None:
- try:
- del os.environ[key]
- except Exception:
- pass
- else:
- os.environ[key] = value
- yield (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output)
- finally:
- for key, value in old_env.items():
- if value is None:
- try:
- del os.environ[key]
- except Exception:
- pass
- else:
- os.environ[key] = value
- sys.stdout = old_stdout
- sys.stderr = old_stderr
- sys.stdin = old_stdin
- termui.visible_prompt_func = old_visible_prompt_func
- termui.hidden_prompt_func = old_hidden_prompt_func
- termui._getchar = old__getchar_func
- utils.should_strip_ansi = old_should_strip_ansi # type: ignore
- _compat.should_strip_ansi = old__compat_should_strip_ansi
- formatting.FORCED_WIDTH = old_forced_width
-
- def invoke(
- self,
- cli: Command,
- args: str | cabc.Sequence[str] | None = None,
- input: str | bytes | t.IO[t.Any] | None = None,
- env: cabc.Mapping[str, str | None] | None = None,
- catch_exceptions: bool | None = None,
- color: bool = False,
- **extra: t.Any,
- ) -> Result:
- """Invokes a command in an isolated environment. The arguments are
- forwarded directly to the command line script, the `extra` keyword
- arguments are passed to the :meth:`~clickpkg.Command.main` function of
- the command.
-
- This returns a :class:`Result` object.
-
- :param cli: the command to invoke
- :param args: the arguments to invoke. It may be given as an iterable
- or a string. When given as string it will be interpreted
- as a Unix shell command. More details at
- :func:`shlex.split`.
- :param input: the input data for `sys.stdin`.
- :param env: the environment overrides.
- :param catch_exceptions: Whether to catch any other exceptions than
- ``SystemExit``. If :data:`None`, the value
- from :class:`CliRunner` is used.
- :param extra: the keyword arguments to pass to :meth:`main`.
- :param color: whether the output should contain color codes. The
- application can still override this explicitly.
-
- .. versionadded:: 8.2
- The result object has the ``output_bytes`` attribute with
- the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would
- see it in its terminal.
-
- .. versionchanged:: 8.2
- The result object always returns the ``stderr_bytes`` stream.
-
- .. versionchanged:: 8.0
- The result object has the ``return_value`` attribute with
- the value returned from the invoked command.
-
- .. versionchanged:: 4.0
- Added the ``color`` parameter.
-
- .. versionchanged:: 3.0
- Added the ``catch_exceptions`` parameter.
-
- .. versionchanged:: 3.0
- The result object has the ``exc_info`` attribute with the
- traceback if available.
- """
- exc_info = None
- if catch_exceptions is None:
- catch_exceptions = self.catch_exceptions
-
- with self.isolation(input=input, env=env, color=color) as outstreams:
- return_value = None
- exception: BaseException | None = None
- exit_code = 0
-
- if isinstance(args, str):
- args = shlex.split(args)
-
- try:
- prog_name = extra.pop("prog_name")
- except KeyError:
- prog_name = self.get_default_prog_name(cli)
-
- try:
- return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
- except SystemExit as e:
- exc_info = sys.exc_info()
- e_code = t.cast("int | t.Any | None", e.code)
-
- if e_code is None:
- e_code = 0
-
- if e_code != 0:
- exception = e
-
- if not isinstance(e_code, int):
- sys.stdout.write(str(e_code))
- sys.stdout.write("\n")
- e_code = 1
-
- exit_code = e_code
-
- except Exception as e:
- if not catch_exceptions:
- raise
- exception = e
- exit_code = 1
- exc_info = sys.exc_info()
- finally:
- sys.stdout.flush()
- sys.stderr.flush()
- stdout = outstreams[0].getvalue()
- stderr = outstreams[1].getvalue()
- output = outstreams[2].getvalue()
-
- return Result(
- runner=self,
- stdout_bytes=stdout,
- stderr_bytes=stderr,
- output_bytes=output,
- return_value=return_value,
- exit_code=exit_code,
- exception=exception,
- exc_info=exc_info, # type: ignore
- )
-
- @contextlib.contextmanager
- def isolated_filesystem(
- self, temp_dir: str | os.PathLike[str] | None = None
- ) -> cabc.Iterator[str]:
- """A context manager that creates a temporary directory and
- changes the current working directory to it. This isolates tests
- that affect the contents of the CWD to prevent them from
- interfering with each other.
-
- :param temp_dir: Create the temporary directory under this
- directory. If given, the created directory is not removed
- when exiting.
-
- .. versionchanged:: 8.0
- Added the ``temp_dir`` parameter.
- """
- cwd = os.getcwd()
- dt = tempfile.mkdtemp(dir=temp_dir)
- os.chdir(dt)
-
- try:
- yield dt
- finally:
- os.chdir(cwd)
-
- if temp_dir is None:
- import shutil
-
- try:
- shutil.rmtree(dt)
- except OSError:
- pass
diff --git a/backend/.venv/lib/python3.12/site-packages/click/types.py b/backend/.venv/lib/python3.12/site-packages/click/types.py
deleted file mode 100644
index e71c1c2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/types.py
+++ /dev/null
@@ -1,1209 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import enum
-import os
-import stat
-import sys
-import typing as t
-from datetime import datetime
-from gettext import gettext as _
-from gettext import ngettext
-
-from ._compat import _get_argv_encoding
-from ._compat import open_stream
-from .exceptions import BadParameter
-from .utils import format_filename
-from .utils import LazyFile
-from .utils import safecall
-
-if t.TYPE_CHECKING:
- import typing_extensions as te
-
- from .core import Context
- from .core import Parameter
- from .shell_completion import CompletionItem
-
-ParamTypeValue = t.TypeVar("ParamTypeValue")
-
-
-class ParamType:
- """Represents the type of a parameter. Validates and converts values
- from the command line or Python into the correct type.
-
- To implement a custom type, subclass and implement at least the
- following:
-
- - The :attr:`name` class attribute must be set.
- - Calling an instance of the type with ``None`` must return
- ``None``. This is already implemented by default.
- - :meth:`convert` must convert string values to the correct type.
- - :meth:`convert` must accept values that are already the correct
- type.
- - It must be able to convert a value if the ``ctx`` and ``param``
- arguments are ``None``. This can occur when converting prompt
- input.
- """
-
- is_composite: t.ClassVar[bool] = False
- arity: t.ClassVar[int] = 1
-
- #: the descriptive name of this type
- name: str
-
- #: if a list of this type is expected and the value is pulled from a
- #: string environment variable, this is what splits it up. `None`
- #: means any whitespace. For all parameters the general rule is that
- #: whitespace splits them up. The exception are paths and files which
- #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
- #: Windows).
- envvar_list_splitter: t.ClassVar[str | None] = None
-
- def to_info_dict(self) -> dict[str, t.Any]:
- """Gather information that could be useful for a tool generating
- user-facing documentation.
-
- Use :meth:`click.Context.to_info_dict` to traverse the entire
- CLI structure.
-
- .. versionadded:: 8.0
- """
- # The class name without the "ParamType" suffix.
- param_type = type(self).__name__.partition("ParamType")[0]
- param_type = param_type.partition("ParameterType")[0]
-
- # Custom subclasses might not remember to set a name.
- if hasattr(self, "name"):
- name = self.name
- else:
- name = param_type
-
- return {"param_type": param_type, "name": name}
-
- def __call__(
- self,
- value: t.Any,
- param: Parameter | None = None,
- ctx: Context | None = None,
- ) -> t.Any:
- if value is not None:
- return self.convert(value, param, ctx)
-
- def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
- """Returns the metavar default for this param if it provides one."""
-
- def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None:
- """Optionally might return extra information about a missing
- parameter.
-
- .. versionadded:: 2.0
- """
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- """Convert the value to the correct type. This is not called if
- the value is ``None`` (the missing value).
-
- This must accept string values from the command line, as well as
- values that are already the correct type. It may also convert
- other compatible types.
-
- The ``param`` and ``ctx`` arguments may be ``None`` in certain
- situations, such as when converting prompt input.
-
- If the value cannot be converted, call :meth:`fail` with a
- descriptive message.
-
- :param value: The value to convert.
- :param param: The parameter that is using this type to convert
- its value. May be ``None``.
- :param ctx: The current context that arrived at this value. May
- be ``None``.
- """
- return value
-
- def split_envvar_value(self, rv: str) -> cabc.Sequence[str]:
- """Given a value from an environment variable this splits it up
- into small chunks depending on the defined envvar list splitter.
-
- If the splitter is set to `None`, which means that whitespace splits,
- then leading and trailing whitespace is ignored. Otherwise, leading
- and trailing splitters usually lead to empty items being included.
- """
- return (rv or "").split(self.envvar_list_splitter)
-
- def fail(
- self,
- message: str,
- param: Parameter | None = None,
- ctx: Context | None = None,
- ) -> t.NoReturn:
- """Helper method to fail with an invalid value message."""
- raise BadParameter(message, ctx=ctx, param=param)
-
- def shell_complete(
- self, ctx: Context, param: Parameter, incomplete: str
- ) -> list[CompletionItem]:
- """Return a list of
- :class:`~click.shell_completion.CompletionItem` objects for the
- incomplete value. Most types do not provide completions, but
- some do, and this allows custom types to provide custom
- completions as well.
-
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- return []
-
-
-class CompositeParamType(ParamType):
- is_composite = True
-
- @property
- def arity(self) -> int: # type: ignore
- raise NotImplementedError()
-
-
-class FuncParamType(ParamType):
- def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
- self.name: str = func.__name__
- self.func = func
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["func"] = self.func
- return info_dict
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- try:
- return self.func(value)
- except ValueError:
- try:
- value = str(value)
- except UnicodeError:
- value = value.decode("utf-8", "replace")
-
- self.fail(value, param, ctx)
-
-
-class UnprocessedParamType(ParamType):
- name = "text"
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- return value
-
- def __repr__(self) -> str:
- return "UNPROCESSED"
-
-
-class StringParamType(ParamType):
- name = "text"
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- if isinstance(value, bytes):
- enc = _get_argv_encoding()
- try:
- value = value.decode(enc)
- except UnicodeError:
- fs_enc = sys.getfilesystemencoding()
- if fs_enc != enc:
- try:
- value = value.decode(fs_enc)
- except UnicodeError:
- value = value.decode("utf-8", "replace")
- else:
- value = value.decode("utf-8", "replace")
- return value
- return str(value)
-
- def __repr__(self) -> str:
- return "STRING"
-
-
-class Choice(ParamType, t.Generic[ParamTypeValue]):
- """The choice type allows a value to be checked against a fixed set
- of supported values.
-
- You may pass any iterable value which will be converted to a tuple
- and thus will only be iterated once.
-
- The resulting value will always be one of the originally passed choices.
- See :meth:`normalize_choice` for more info on the mapping of strings
- to choices. See :ref:`choice-opts` for an example.
-
- :param case_sensitive: Set to false to make choices case
- insensitive. Defaults to true.
-
- .. versionchanged:: 8.2.0
- Non-``str`` ``choices`` are now supported. It can additionally be any
- iterable. Before you were not recommended to pass anything but a list or
- tuple.
-
- .. versionadded:: 8.2.0
- Choice normalization can be overridden via :meth:`normalize_choice`.
- """
-
- name = "choice"
-
- def __init__(
- self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True
- ) -> None:
- self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices)
- self.case_sensitive = case_sensitive
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["choices"] = self.choices
- info_dict["case_sensitive"] = self.case_sensitive
- return info_dict
-
- def _normalized_mapping(
- self, ctx: Context | None = None
- ) -> cabc.Mapping[ParamTypeValue, str]:
- """
- Returns mapping where keys are the original choices and the values are
- the normalized values that are accepted via the command line.
-
- This is a simple wrapper around :meth:`normalize_choice`, use that
- instead which is supported.
- """
- return {
- choice: self.normalize_choice(
- choice=choice,
- ctx=ctx,
- )
- for choice in self.choices
- }
-
- def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str:
- """
- Normalize a choice value, used to map a passed string to a choice.
- Each choice must have a unique normalized value.
-
- By default uses :meth:`Context.token_normalize_func` and if not case
- sensitive, convert it to a casefolded value.
-
- .. versionadded:: 8.2.0
- """
- normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice)
-
- if ctx is not None and ctx.token_normalize_func is not None:
- normed_value = ctx.token_normalize_func(normed_value)
-
- if not self.case_sensitive:
- normed_value = normed_value.casefold()
-
- return normed_value
-
- def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
- if param.param_type_name == "option" and not param.show_choices: # type: ignore
- choice_metavars = [
- convert_type(type(choice)).name.upper() for choice in self.choices
- ]
- choices_str = "|".join([*dict.fromkeys(choice_metavars)])
- else:
- choices_str = "|".join(
- [str(i) for i in self._normalized_mapping(ctx=ctx).values()]
- )
-
- # Use curly braces to indicate a required argument.
- if param.required and param.param_type_name == "argument":
- return f"{{{choices_str}}}"
-
- # Use square braces to indicate an option or optional argument.
- return f"[{choices_str}]"
-
- def get_missing_message(self, param: Parameter, ctx: Context | None) -> str:
- """
- Message shown when no choice is passed.
-
- .. versionchanged:: 8.2.0 Added ``ctx`` argument.
- """
- return _("Choose from:\n\t{choices}").format(
- choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values())
- )
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> ParamTypeValue:
- """
- For a given value from the parser, normalize it and find its
- matching normalized value in the list of choices. Then return the
- matched "original" choice.
- """
- normed_value = self.normalize_choice(choice=value, ctx=ctx)
- normalized_mapping = self._normalized_mapping(ctx=ctx)
-
- try:
- return next(
- original
- for original, normalized in normalized_mapping.items()
- if normalized == normed_value
- )
- except StopIteration:
- self.fail(
- self.get_invalid_choice_message(value=value, ctx=ctx),
- param=param,
- ctx=ctx,
- )
-
- def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str:
- """Get the error message when the given choice is invalid.
-
- :param value: The invalid value.
-
- .. versionadded:: 8.2
- """
- choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values()))
- return ngettext(
- "{value!r} is not {choice}.",
- "{value!r} is not one of {choices}.",
- len(self.choices),
- ).format(value=value, choice=choices_str, choices=choices_str)
-
- def __repr__(self) -> str:
- return f"Choice({list(self.choices)})"
-
- def shell_complete(
- self, ctx: Context, param: Parameter, incomplete: str
- ) -> list[CompletionItem]:
- """Complete choices that start with the incomplete value.
-
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
-
- str_choices = map(str, self.choices)
-
- if self.case_sensitive:
- matched = (c for c in str_choices if c.startswith(incomplete))
- else:
- incomplete = incomplete.lower()
- matched = (c for c in str_choices if c.lower().startswith(incomplete))
-
- return [CompletionItem(c) for c in matched]
-
-
-class DateTime(ParamType):
- """The DateTime type converts date strings into `datetime` objects.
-
- The format strings which are checked are configurable, but default to some
- common (non-timezone aware) ISO 8601 formats.
-
- When specifying *DateTime* formats, you should only pass a list or a tuple.
- Other iterables, like generators, may lead to surprising results.
-
- The format strings are processed using ``datetime.strptime``, and this
- consequently defines the format strings which are allowed.
-
- Parsing is tried using each format, in order, and the first format which
- parses successfully is used.
-
- :param formats: A list or tuple of date format strings, in the order in
- which they should be tried. Defaults to
- ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
- ``'%Y-%m-%d %H:%M:%S'``.
- """
-
- name = "datetime"
-
- def __init__(self, formats: cabc.Sequence[str] | None = None):
- self.formats: cabc.Sequence[str] = formats or [
- "%Y-%m-%d",
- "%Y-%m-%dT%H:%M:%S",
- "%Y-%m-%d %H:%M:%S",
- ]
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["formats"] = self.formats
- return info_dict
-
- def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
- return f"[{'|'.join(self.formats)}]"
-
- def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None:
- try:
- return datetime.strptime(value, format)
- except ValueError:
- return None
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- if isinstance(value, datetime):
- return value
-
- for format in self.formats:
- converted = self._try_to_convert_date(value, format)
-
- if converted is not None:
- return converted
-
- formats_str = ", ".join(map(repr, self.formats))
- self.fail(
- ngettext(
- "{value!r} does not match the format {format}.",
- "{value!r} does not match the formats {formats}.",
- len(self.formats),
- ).format(value=value, format=formats_str, formats=formats_str),
- param,
- ctx,
- )
-
- def __repr__(self) -> str:
- return "DateTime"
-
-
-class _NumberParamTypeBase(ParamType):
- _number_class: t.ClassVar[type[t.Any]]
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- try:
- return self._number_class(value)
- except ValueError:
- self.fail(
- _("{value!r} is not a valid {number_type}.").format(
- value=value, number_type=self.name
- ),
- param,
- ctx,
- )
-
-
-class _NumberRangeBase(_NumberParamTypeBase):
- def __init__(
- self,
- min: float | None = None,
- max: float | None = None,
- min_open: bool = False,
- max_open: bool = False,
- clamp: bool = False,
- ) -> None:
- self.min = min
- self.max = max
- self.min_open = min_open
- self.max_open = max_open
- self.clamp = clamp
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(
- min=self.min,
- max=self.max,
- min_open=self.min_open,
- max_open=self.max_open,
- clamp=self.clamp,
- )
- return info_dict
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- import operator
-
- rv = super().convert(value, param, ctx)
- lt_min: bool = self.min is not None and (
- operator.le if self.min_open else operator.lt
- )(rv, self.min)
- gt_max: bool = self.max is not None and (
- operator.ge if self.max_open else operator.gt
- )(rv, self.max)
-
- if self.clamp:
- if lt_min:
- return self._clamp(self.min, 1, self.min_open) # type: ignore
-
- if gt_max:
- return self._clamp(self.max, -1, self.max_open) # type: ignore
-
- if lt_min or gt_max:
- self.fail(
- _("{value} is not in the range {range}.").format(
- value=rv, range=self._describe_range()
- ),
- param,
- ctx,
- )
-
- return rv
-
- def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
- """Find the valid value to clamp to bound in the given
- direction.
-
- :param bound: The boundary value.
- :param dir: 1 or -1 indicating the direction to move.
- :param open: If true, the range does not include the bound.
- """
- raise NotImplementedError
-
- def _describe_range(self) -> str:
- """Describe the range for use in help text."""
- if self.min is None:
- op = "<" if self.max_open else "<="
- return f"x{op}{self.max}"
-
- if self.max is None:
- op = ">" if self.min_open else ">="
- return f"x{op}{self.min}"
-
- lop = "<" if self.min_open else "<="
- rop = "<" if self.max_open else "<="
- return f"{self.min}{lop}x{rop}{self.max}"
-
- def __repr__(self) -> str:
- clamp = " clamped" if self.clamp else ""
- return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
-
-
-class IntParamType(_NumberParamTypeBase):
- name = "integer"
- _number_class = int
-
- def __repr__(self) -> str:
- return "INT"
-
-
-class IntRange(_NumberRangeBase, IntParamType):
- """Restrict an :data:`click.INT` value to a range of accepted
- values. See :ref:`ranges`.
-
- If ``min`` or ``max`` are not passed, any value is accepted in that
- direction. If ``min_open`` or ``max_open`` are enabled, the
- corresponding boundary is not included in the range.
-
- If ``clamp`` is enabled, a value outside the range is clamped to the
- boundary instead of failing.
-
- .. versionchanged:: 8.0
- Added the ``min_open`` and ``max_open`` parameters.
- """
-
- name = "integer range"
-
- def _clamp( # type: ignore
- self, bound: int, dir: t.Literal[1, -1], open: bool
- ) -> int:
- if not open:
- return bound
-
- return bound + dir
-
-
-class FloatParamType(_NumberParamTypeBase):
- name = "float"
- _number_class = float
-
- def __repr__(self) -> str:
- return "FLOAT"
-
-
-class FloatRange(_NumberRangeBase, FloatParamType):
- """Restrict a :data:`click.FLOAT` value to a range of accepted
- values. See :ref:`ranges`.
-
- If ``min`` or ``max`` are not passed, any value is accepted in that
- direction. If ``min_open`` or ``max_open`` are enabled, the
- corresponding boundary is not included in the range.
-
- If ``clamp`` is enabled, a value outside the range is clamped to the
- boundary instead of failing. This is not supported if either
- boundary is marked ``open``.
-
- .. versionchanged:: 8.0
- Added the ``min_open`` and ``max_open`` parameters.
- """
-
- name = "float range"
-
- def __init__(
- self,
- min: float | None = None,
- max: float | None = None,
- min_open: bool = False,
- max_open: bool = False,
- clamp: bool = False,
- ) -> None:
- super().__init__(
- min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
- )
-
- if (min_open or max_open) and clamp:
- raise TypeError("Clamping is not supported for open bounds.")
-
- def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
- if not open:
- return bound
-
- # Could use math.nextafter here, but clamping an
- # open float range doesn't seem to be particularly useful. It's
- # left up to the user to write a callback to do it if needed.
- raise RuntimeError("Clamping is not supported for open bounds.")
-
-
-class BoolParamType(ParamType):
- name = "boolean"
-
- bool_states: dict[str, bool] = {
- "1": True,
- "0": False,
- "yes": True,
- "no": False,
- "true": True,
- "false": False,
- "on": True,
- "off": False,
- "t": True,
- "f": False,
- "y": True,
- "n": False,
- # Absence of value is considered False.
- "": False,
- }
- """A mapping of string values to boolean states.
-
- Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES`
- and extends it.
-
- .. caution::
- String values are lower-cased, as the ``str_to_bool`` comparison function
- below is case-insensitive.
-
- .. warning::
- The mapping is not exhaustive, and does not cover all possible boolean strings
- representations. It will remains as it is to avoid endless bikeshedding.
-
- Future work my be considered to make this mapping user-configurable from public
- API.
- """
-
- @staticmethod
- def str_to_bool(value: str | bool) -> bool | None:
- """Convert a string to a boolean value.
-
- If the value is already a boolean, it is returned as-is. If the value is a
- string, it is stripped of whitespaces and lower-cased, then checked against
- the known boolean states pre-defined in the `BoolParamType.bool_states` mapping
- above.
-
- Returns `None` if the value does not match any known boolean state.
- """
- if isinstance(value, bool):
- return value
- return BoolParamType.bool_states.get(value.strip().lower())
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> bool:
- normalized = self.str_to_bool(value)
- if normalized is None:
- self.fail(
- _(
- "{value!r} is not a valid boolean. Recognized values: {states}"
- ).format(value=value, states=", ".join(sorted(self.bool_states))),
- param,
- ctx,
- )
- return normalized
-
- def __repr__(self) -> str:
- return "BOOL"
-
-
-class UUIDParameterType(ParamType):
- name = "uuid"
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- import uuid
-
- if isinstance(value, uuid.UUID):
- return value
-
- value = value.strip()
-
- try:
- return uuid.UUID(value)
- except ValueError:
- self.fail(
- _("{value!r} is not a valid UUID.").format(value=value), param, ctx
- )
-
- def __repr__(self) -> str:
- return "UUID"
-
-
-class File(ParamType):
- """Declares a parameter to be a file for reading or writing. The file
- is automatically closed once the context tears down (after the command
- finished working).
-
- Files can be opened for reading or writing. The special value ``-``
- indicates stdin or stdout depending on the mode.
-
- By default, the file is opened for reading text data, but it can also be
- opened in binary mode or for writing. The encoding parameter can be used
- to force a specific encoding.
-
- The `lazy` flag controls if the file should be opened immediately or upon
- first IO. The default is to be non-lazy for standard input and output
- streams as well as files opened for reading, `lazy` otherwise. When opening a
- file lazily for reading, it is still opened temporarily for validation, but
- will not be held open until first IO. lazy is mainly useful when opening
- for writing to avoid creating the file until it is needed.
-
- Files can also be opened atomically in which case all writes go into a
- separate file in the same folder and upon completion the file will
- be moved over to the original location. This is useful if a file
- regularly read by other users is modified.
-
- See :ref:`file-args` for more information.
-
- .. versionchanged:: 2.0
- Added the ``atomic`` parameter.
- """
-
- name = "filename"
- envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
-
- def __init__(
- self,
- mode: str = "r",
- encoding: str | None = None,
- errors: str | None = "strict",
- lazy: bool | None = None,
- atomic: bool = False,
- ) -> None:
- self.mode = mode
- self.encoding = encoding
- self.errors = errors
- self.lazy = lazy
- self.atomic = atomic
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(mode=self.mode, encoding=self.encoding)
- return info_dict
-
- def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool:
- if self.lazy is not None:
- return self.lazy
- if os.fspath(value) == "-":
- return False
- elif "w" in self.mode:
- return True
- return False
-
- def convert(
- self,
- value: str | os.PathLike[str] | t.IO[t.Any],
- param: Parameter | None,
- ctx: Context | None,
- ) -> t.IO[t.Any]:
- if _is_file_like(value):
- return value
-
- value = t.cast("str | os.PathLike[str]", value)
-
- try:
- lazy = self.resolve_lazy_flag(value)
-
- if lazy:
- lf = LazyFile(
- value, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
-
- if ctx is not None:
- ctx.call_on_close(lf.close_intelligently)
-
- return t.cast("t.IO[t.Any]", lf)
-
- f, should_close = open_stream(
- value, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
-
- # If a context is provided, we automatically close the file
- # at the end of the context execution (or flush out). If a
- # context does not exist, it's the caller's responsibility to
- # properly close the file. This for instance happens when the
- # type is used with prompts.
- if ctx is not None:
- if should_close:
- ctx.call_on_close(safecall(f.close))
- else:
- ctx.call_on_close(safecall(f.flush))
-
- return f
- except OSError as e:
- self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
-
- def shell_complete(
- self, ctx: Context, param: Parameter, incomplete: str
- ) -> list[CompletionItem]:
- """Return a special completion marker that tells the completion
- system to use the shell to provide file path completions.
-
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
-
- return [CompletionItem(incomplete, type="file")]
-
-
-def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]:
- return hasattr(value, "read") or hasattr(value, "write")
-
-
-class Path(ParamType):
- """The ``Path`` type is similar to the :class:`File` type, but
- returns the filename instead of an open file. Various checks can be
- enabled to validate the type of file and permissions.
-
- :param exists: The file or directory needs to exist for the value to
- be valid. If this is not set to ``True``, and the file does not
- exist, then all further checks are silently skipped.
- :param file_okay: Allow a file as a value.
- :param dir_okay: Allow a directory as a value.
- :param readable: if true, a readable check is performed.
- :param writable: if true, a writable check is performed.
- :param executable: if true, an executable check is performed.
- :param resolve_path: Make the value absolute and resolve any
- symlinks. A ``~`` is not expanded, as this is supposed to be
- done by the shell only.
- :param allow_dash: Allow a single dash as a value, which indicates
- a standard stream (but does not open it). Use
- :func:`~click.open_file` to handle opening this value.
- :param path_type: Convert the incoming path value to this type. If
- ``None``, keep Python's default, which is ``str``. Useful to
- convert to :class:`pathlib.Path`.
-
- .. versionchanged:: 8.1
- Added the ``executable`` parameter.
-
- .. versionchanged:: 8.0
- Allow passing ``path_type=pathlib.Path``.
-
- .. versionchanged:: 6.0
- Added the ``allow_dash`` parameter.
- """
-
- envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
-
- def __init__(
- self,
- exists: bool = False,
- file_okay: bool = True,
- dir_okay: bool = True,
- writable: bool = False,
- readable: bool = True,
- resolve_path: bool = False,
- allow_dash: bool = False,
- path_type: type[t.Any] | None = None,
- executable: bool = False,
- ):
- self.exists = exists
- self.file_okay = file_okay
- self.dir_okay = dir_okay
- self.readable = readable
- self.writable = writable
- self.executable = executable
- self.resolve_path = resolve_path
- self.allow_dash = allow_dash
- self.type = path_type
-
- if self.file_okay and not self.dir_okay:
- self.name: str = _("file")
- elif self.dir_okay and not self.file_okay:
- self.name = _("directory")
- else:
- self.name = _("path")
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict.update(
- exists=self.exists,
- file_okay=self.file_okay,
- dir_okay=self.dir_okay,
- writable=self.writable,
- readable=self.readable,
- allow_dash=self.allow_dash,
- )
- return info_dict
-
- def coerce_path_result(
- self, value: str | os.PathLike[str]
- ) -> str | bytes | os.PathLike[str]:
- if self.type is not None and not isinstance(value, self.type):
- if self.type is str:
- return os.fsdecode(value)
- elif self.type is bytes:
- return os.fsencode(value)
- else:
- return t.cast("os.PathLike[str]", self.type(value))
-
- return value
-
- def convert(
- self,
- value: str | os.PathLike[str],
- param: Parameter | None,
- ctx: Context | None,
- ) -> str | bytes | os.PathLike[str]:
- rv = value
-
- is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
-
- if not is_dash:
- if self.resolve_path:
- rv = os.path.realpath(rv)
-
- try:
- st = os.stat(rv)
- except OSError:
- if not self.exists:
- return self.coerce_path_result(rv)
- self.fail(
- _("{name} {filename!r} does not exist.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
-
- if not self.file_okay and stat.S_ISREG(st.st_mode):
- self.fail(
- _("{name} {filename!r} is a file.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
- if not self.dir_okay and stat.S_ISDIR(st.st_mode):
- self.fail(
- _("{name} {filename!r} is a directory.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
-
- if self.readable and not os.access(rv, os.R_OK):
- self.fail(
- _("{name} {filename!r} is not readable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
-
- if self.writable and not os.access(rv, os.W_OK):
- self.fail(
- _("{name} {filename!r} is not writable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
-
- if self.executable and not os.access(value, os.X_OK):
- self.fail(
- _("{name} {filename!r} is not executable.").format(
- name=self.name.title(), filename=format_filename(value)
- ),
- param,
- ctx,
- )
-
- return self.coerce_path_result(rv)
-
- def shell_complete(
- self, ctx: Context, param: Parameter, incomplete: str
- ) -> list[CompletionItem]:
- """Return a special completion marker that tells the completion
- system to use the shell to provide path completions for only
- directories or any paths.
-
- :param ctx: Invocation context for this command.
- :param param: The parameter that is requesting completion.
- :param incomplete: Value being completed. May be empty.
-
- .. versionadded:: 8.0
- """
- from click.shell_completion import CompletionItem
-
- type = "dir" if self.dir_okay and not self.file_okay else "file"
- return [CompletionItem(incomplete, type=type)]
-
-
-class Tuple(CompositeParamType):
- """The default behavior of Click is to apply a type on a value directly.
- This works well in most cases, except for when `nargs` is set to a fixed
- count and different types should be used for different items. In this
- case the :class:`Tuple` type can be used. This type can only be used
- if `nargs` is set to a fixed number.
-
- For more information see :ref:`tuple-type`.
-
- This can be selected by using a Python tuple literal as a type.
-
- :param types: a list of types that should be used for the tuple items.
- """
-
- def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None:
- self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types]
-
- def to_info_dict(self) -> dict[str, t.Any]:
- info_dict = super().to_info_dict()
- info_dict["types"] = [t.to_info_dict() for t in self.types]
- return info_dict
-
- @property
- def name(self) -> str: # type: ignore
- return f"<{' '.join(ty.name for ty in self.types)}>"
-
- @property
- def arity(self) -> int: # type: ignore
- return len(self.types)
-
- def convert(
- self, value: t.Any, param: Parameter | None, ctx: Context | None
- ) -> t.Any:
- len_type = len(self.types)
- len_value = len(value)
-
- if len_value != len_type:
- self.fail(
- ngettext(
- "{len_type} values are required, but {len_value} was given.",
- "{len_type} values are required, but {len_value} were given.",
- len_value,
- ).format(len_type=len_type, len_value=len_value),
- param=param,
- ctx=ctx,
- )
-
- return tuple(
- ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False)
- )
-
-
-def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType:
- """Find the most appropriate :class:`ParamType` for the given Python
- type. If the type isn't provided, it can be inferred from a default
- value.
- """
- guessed_type = False
-
- if ty is None and default is not None:
- if isinstance(default, (tuple, list)):
- # If the default is empty, ty will remain None and will
- # return STRING.
- if default:
- item = default[0]
-
- # A tuple of tuples needs to detect the inner types.
- # Can't call convert recursively because that would
- # incorrectly unwind the tuple to a single type.
- if isinstance(item, (tuple, list)):
- ty = tuple(map(type, item))
- else:
- ty = type(item)
- else:
- ty = type(default)
-
- guessed_type = True
-
- if isinstance(ty, tuple):
- return Tuple(ty)
-
- if isinstance(ty, ParamType):
- return ty
-
- if ty is str or ty is None:
- return STRING
-
- if ty is int:
- return INT
-
- if ty is float:
- return FLOAT
-
- if ty is bool:
- return BOOL
-
- if guessed_type:
- return STRING
-
- if __debug__:
- try:
- if issubclass(ty, ParamType):
- raise AssertionError(
- f"Attempted to use an uninstantiated parameter type ({ty})."
- )
- except TypeError:
- # ty is an instance (correct), so issubclass fails.
- pass
-
- return FuncParamType(ty)
-
-
-#: A dummy parameter type that just does nothing. From a user's
-#: perspective this appears to just be the same as `STRING` but
-#: internally no string conversion takes place if the input was bytes.
-#: This is usually useful when working with file paths as they can
-#: appear in bytes and unicode.
-#:
-#: For path related uses the :class:`Path` type is a better choice but
-#: there are situations where an unprocessed type is useful which is why
-#: it is is provided.
-#:
-#: .. versionadded:: 4.0
-UNPROCESSED = UnprocessedParamType()
-
-#: A unicode string parameter type which is the implicit default. This
-#: can also be selected by using ``str`` as type.
-STRING = StringParamType()
-
-#: An integer parameter. This can also be selected by using ``int`` as
-#: type.
-INT = IntParamType()
-
-#: A floating point value parameter. This can also be selected by using
-#: ``float`` as type.
-FLOAT = FloatParamType()
-
-#: A boolean parameter. This is the default for boolean flags. This can
-#: also be selected by using ``bool`` as a type.
-BOOL = BoolParamType()
-
-#: A UUID parameter.
-UUID = UUIDParameterType()
-
-
-class OptionHelpExtra(t.TypedDict, total=False):
- envvars: tuple[str, ...]
- default: str
- range: str
- required: str
diff --git a/backend/.venv/lib/python3.12/site-packages/click/utils.py b/backend/.venv/lib/python3.12/site-packages/click/utils.py
deleted file mode 100644
index beae26f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/click/utils.py
+++ /dev/null
@@ -1,627 +0,0 @@
-from __future__ import annotations
-
-import collections.abc as cabc
-import os
-import re
-import sys
-import typing as t
-from functools import update_wrapper
-from types import ModuleType
-from types import TracebackType
-
-from ._compat import _default_text_stderr
-from ._compat import _default_text_stdout
-from ._compat import _find_binary_writer
-from ._compat import auto_wrap_for_ansi
-from ._compat import binary_streams
-from ._compat import open_stream
-from ._compat import should_strip_ansi
-from ._compat import strip_ansi
-from ._compat import text_streams
-from ._compat import WIN
-from .globals import resolve_color_default
-
-if t.TYPE_CHECKING:
- import typing_extensions as te
-
- P = te.ParamSpec("P")
-
-R = t.TypeVar("R")
-
-
-def _posixify(name: str) -> str:
- return "-".join(name.split()).lower()
-
-
-def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]:
- """Wraps a function so that it swallows exceptions."""
-
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None:
- try:
- return func(*args, **kwargs)
- except Exception:
- pass
- return None
-
- return update_wrapper(wrapper, func)
-
-
-def make_str(value: t.Any) -> str:
- """Converts a value into a valid string."""
- if isinstance(value, bytes):
- try:
- return value.decode(sys.getfilesystemencoding())
- except UnicodeError:
- return value.decode("utf-8", "replace")
- return str(value)
-
-
-def make_default_short_help(help: str, max_length: int = 45) -> str:
- """Returns a condensed version of help string."""
- # Consider only the first paragraph.
- paragraph_end = help.find("\n\n")
-
- if paragraph_end != -1:
- help = help[:paragraph_end]
-
- # Collapse newlines, tabs, and spaces.
- words = help.split()
-
- if not words:
- return ""
-
- # The first paragraph started with a "no rewrap" marker, ignore it.
- if words[0] == "\b":
- words = words[1:]
-
- total_length = 0
- last_index = len(words) - 1
-
- for i, word in enumerate(words):
- total_length += len(word) + (i > 0)
-
- if total_length > max_length: # too long, truncate
- break
-
- if word[-1] == ".": # sentence end, truncate without "..."
- return " ".join(words[: i + 1])
-
- if total_length == max_length and i != last_index:
- break # not at sentence end, truncate with "..."
- else:
- return " ".join(words) # no truncation needed
-
- # Account for the length of the suffix.
- total_length += len("...")
-
- # remove words until the length is short enough
- while i > 0:
- total_length -= len(words[i]) + (i > 0)
-
- if total_length <= max_length:
- break
-
- i -= 1
-
- return " ".join(words[:i]) + "..."
-
-
-class LazyFile:
- """A lazy file works like a regular file but it does not fully open
- the file but it does perform some basic checks early to see if the
- filename parameter does make sense. This is useful for safely opening
- files for writing.
- """
-
- def __init__(
- self,
- filename: str | os.PathLike[str],
- mode: str = "r",
- encoding: str | None = None,
- errors: str | None = "strict",
- atomic: bool = False,
- ):
- self.name: str = os.fspath(filename)
- self.mode = mode
- self.encoding = encoding
- self.errors = errors
- self.atomic = atomic
- self._f: t.IO[t.Any] | None
- self.should_close: bool
-
- if self.name == "-":
- self._f, self.should_close = open_stream(filename, mode, encoding, errors)
- else:
- if "r" in mode:
- # Open and close the file in case we're opening it for
- # reading so that we can catch at least some errors in
- # some cases early.
- open(filename, mode).close()
- self._f = None
- self.should_close = True
-
- def __getattr__(self, name: str) -> t.Any:
- return getattr(self.open(), name)
-
- def __repr__(self) -> str:
- if self._f is not None:
- return repr(self._f)
- return f""
-
- def open(self) -> t.IO[t.Any]:
- """Opens the file if it's not yet open. This call might fail with
- a :exc:`FileError`. Not handling this error will produce an error
- that Click shows.
- """
- if self._f is not None:
- return self._f
- try:
- rv, self.should_close = open_stream(
- self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
- except OSError as e:
- from .exceptions import FileError
-
- raise FileError(self.name, hint=e.strerror) from e
- self._f = rv
- return rv
-
- def close(self) -> None:
- """Closes the underlying file, no matter what."""
- if self._f is not None:
- self._f.close()
-
- def close_intelligently(self) -> None:
- """This function only closes the file if it was opened by the lazy
- file wrapper. For instance this will never close stdin.
- """
- if self.should_close:
- self.close()
-
- def __enter__(self) -> LazyFile:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> None:
- self.close_intelligently()
-
- def __iter__(self) -> cabc.Iterator[t.AnyStr]:
- self.open()
- return iter(self._f) # type: ignore
-
-
-class KeepOpenFile:
- def __init__(self, file: t.IO[t.Any]) -> None:
- self._file: t.IO[t.Any] = file
-
- def __getattr__(self, name: str) -> t.Any:
- return getattr(self._file, name)
-
- def __enter__(self) -> KeepOpenFile:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- tb: TracebackType | None,
- ) -> None:
- pass
-
- def __repr__(self) -> str:
- return repr(self._file)
-
- def __iter__(self) -> cabc.Iterator[t.AnyStr]:
- return iter(self._file)
-
-
-def echo(
- message: t.Any | None = None,
- file: t.IO[t.Any] | None = None,
- nl: bool = True,
- err: bool = False,
- color: bool | None = None,
-) -> None:
- """Print a message and newline to stdout or a file. This should be
- used instead of :func:`print` because it provides better support
- for different data, files, and environments.
-
- Compared to :func:`print`, this does the following:
-
- - Ensures that the output encoding is not misconfigured on Linux.
- - Supports Unicode in the Windows console.
- - Supports writing to binary outputs, and supports writing bytes
- to text outputs.
- - Supports colors and styles on Windows.
- - Removes ANSI color and style codes if the output does not look
- like an interactive terminal.
- - Always flushes the output.
-
- :param message: The string or bytes to output. Other objects are
- converted to strings.
- :param file: The file to write to. Defaults to ``stdout``.
- :param err: Write to ``stderr`` instead of ``stdout``.
- :param nl: Print a newline after the message. Enabled by default.
- :param color: Force showing or hiding colors and other styles. By
- default Click will remove color if the output does not look like
- an interactive terminal.
-
- .. versionchanged:: 6.0
- Support Unicode output on the Windows console. Click does not
- modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
- will still not support Unicode.
-
- .. versionchanged:: 4.0
- Added the ``color`` parameter.
-
- .. versionadded:: 3.0
- Added the ``err`` parameter.
-
- .. versionchanged:: 2.0
- Support colors on Windows if colorama is installed.
- """
- if file is None:
- if err:
- file = _default_text_stderr()
- else:
- file = _default_text_stdout()
-
- # There are no standard streams attached to write to. For example,
- # pythonw on Windows.
- if file is None:
- return
-
- # Convert non bytes/text into the native string type.
- if message is not None and not isinstance(message, (str, bytes, bytearray)):
- out: str | bytes | bytearray | None = str(message)
- else:
- out = message
-
- if nl:
- out = out or ""
- if isinstance(out, str):
- out += "\n"
- else:
- out += b"\n"
-
- if not out:
- file.flush()
- return
-
- # If there is a message and the value looks like bytes, we manually
- # need to find the binary stream and write the message in there.
- # This is done separately so that most stream types will work as you
- # would expect. Eg: you can write to StringIO for other cases.
- if isinstance(out, (bytes, bytearray)):
- binary_file = _find_binary_writer(file)
-
- if binary_file is not None:
- file.flush()
- binary_file.write(out)
- binary_file.flush()
- return
-
- # ANSI style code support. For no message or bytes, nothing happens.
- # When outputting to a file instead of a terminal, strip codes.
- else:
- color = resolve_color_default(color)
-
- if should_strip_ansi(file, color):
- out = strip_ansi(out)
- elif WIN:
- if auto_wrap_for_ansi is not None:
- file = auto_wrap_for_ansi(file, color) # type: ignore
- elif not color:
- out = strip_ansi(out)
-
- file.write(out) # type: ignore
- file.flush()
-
-
-def get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO:
- """Returns a system stream for byte processing.
-
- :param name: the name of the stream to open. Valid names are ``'stdin'``,
- ``'stdout'`` and ``'stderr'``
- """
- opener = binary_streams.get(name)
- if opener is None:
- raise TypeError(f"Unknown standard stream '{name}'")
- return opener()
-
-
-def get_text_stream(
- name: t.Literal["stdin", "stdout", "stderr"],
- encoding: str | None = None,
- errors: str | None = "strict",
-) -> t.TextIO:
- """Returns a system stream for text processing. This usually returns
- a wrapped stream around a binary stream returned from
- :func:`get_binary_stream` but it also can take shortcuts for already
- correctly configured streams.
-
- :param name: the name of the stream to open. Valid names are ``'stdin'``,
- ``'stdout'`` and ``'stderr'``
- :param encoding: overrides the detected default encoding.
- :param errors: overrides the default error mode.
- """
- opener = text_streams.get(name)
- if opener is None:
- raise TypeError(f"Unknown standard stream '{name}'")
- return opener(encoding, errors)
-
-
-def open_file(
- filename: str | os.PathLike[str],
- mode: str = "r",
- encoding: str | None = None,
- errors: str | None = "strict",
- lazy: bool = False,
- atomic: bool = False,
-) -> t.IO[t.Any]:
- """Open a file, with extra behavior to handle ``'-'`` to indicate
- a standard stream, lazy open on write, and atomic write. Similar to
- the behavior of the :class:`~click.File` param type.
-
- If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
- wrapped so that using it in a context manager will not close it.
- This makes it possible to use the function without accidentally
- closing a standard stream:
-
- .. code-block:: python
-
- with open_file(filename) as f:
- ...
-
- :param filename: The name or Path of the file to open, or ``'-'`` for
- ``stdin``/``stdout``.
- :param mode: The mode in which to open the file.
- :param encoding: The encoding to decode or encode a file opened in
- text mode.
- :param errors: The error handling mode.
- :param lazy: Wait to open the file until it is accessed. For read
- mode, the file is temporarily opened to raise access errors
- early, then closed until it is read again.
- :param atomic: Write to a temporary file and replace the given file
- on close.
-
- .. versionadded:: 3.0
- """
- if lazy:
- return t.cast(
- "t.IO[t.Any]", LazyFile(filename, mode, encoding, errors, atomic=atomic)
- )
-
- f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
-
- if not should_close:
- f = t.cast("t.IO[t.Any]", KeepOpenFile(f))
-
- return f
-
-
-def format_filename(
- filename: str | bytes | os.PathLike[str] | os.PathLike[bytes],
- shorten: bool = False,
-) -> str:
- """Format a filename as a string for display. Ensures the filename can be
- displayed by replacing any invalid bytes or surrogate escapes in the name
- with the replacement character ``�``.
-
- Invalid bytes or surrogate escapes will raise an error when written to a
- stream with ``errors="strict"``. This will typically happen with ``stdout``
- when the locale is something like ``en_GB.UTF-8``.
-
- Many scenarios *are* safe to write surrogates though, due to PEP 538 and
- PEP 540, including:
-
- - Writing to ``stderr``, which uses ``errors="backslashreplace"``.
- - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
- stdout and stderr with ``errors="surrogateescape"``.
- - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
- - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
- Python opens stdout and stderr with ``errors="surrogateescape"``.
-
- :param filename: formats a filename for UI display. This will also convert
- the filename into unicode without failing.
- :param shorten: this optionally shortens the filename to strip of the
- path that leads up to it.
- """
- if shorten:
- filename = os.path.basename(filename)
- else:
- filename = os.fspath(filename)
-
- if isinstance(filename, bytes):
- filename = filename.decode(sys.getfilesystemencoding(), "replace")
- else:
- filename = filename.encode("utf-8", "surrogateescape").decode(
- "utf-8", "replace"
- )
-
- return filename
-
-
-def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
- r"""Returns the config folder for the application. The default behavior
- is to return whatever is most appropriate for the operating system.
-
- To give you an idea, for an app called ``"Foo Bar"``, something like
- the following folders could be returned:
-
- Mac OS X:
- ``~/Library/Application Support/Foo Bar``
- Mac OS X (POSIX):
- ``~/.foo-bar``
- Unix:
- ``~/.config/foo-bar``
- Unix (POSIX):
- ``~/.foo-bar``
- Windows (roaming):
- ``C:\Users\\AppData\Roaming\Foo Bar``
- Windows (not roaming):
- ``C:\Users\\AppData\Local\Foo Bar``
-
- .. versionadded:: 2.0
-
- :param app_name: the application name. This should be properly capitalized
- and can contain whitespace.
- :param roaming: controls if the folder should be roaming or not on Windows.
- Has no effect otherwise.
- :param force_posix: if this is set to `True` then on any POSIX system the
- folder will be stored in the home folder with a leading
- dot instead of the XDG config home or darwin's
- application support folder.
- """
- if WIN:
- key = "APPDATA" if roaming else "LOCALAPPDATA"
- folder = os.environ.get(key)
- if folder is None:
- folder = os.path.expanduser("~")
- return os.path.join(folder, app_name)
- if force_posix:
- return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
- if sys.platform == "darwin":
- return os.path.join(
- os.path.expanduser("~/Library/Application Support"), app_name
- )
- return os.path.join(
- os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
- _posixify(app_name),
- )
-
-
-class PacifyFlushWrapper:
- """This wrapper is used to catch and suppress BrokenPipeErrors resulting
- from ``.flush()`` being called on broken pipe during the shutdown/final-GC
- of the Python interpreter. Notably ``.flush()`` is always called on
- ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
- other cleanup code, and the case where the underlying file is not a broken
- pipe, all calls and attributes are proxied.
- """
-
- def __init__(self, wrapped: t.IO[t.Any]) -> None:
- self.wrapped = wrapped
-
- def flush(self) -> None:
- try:
- self.wrapped.flush()
- except OSError as e:
- import errno
-
- if e.errno != errno.EPIPE:
- raise
-
- def __getattr__(self, attr: str) -> t.Any:
- return getattr(self.wrapped, attr)
-
-
-def _detect_program_name(
- path: str | None = None, _main: ModuleType | None = None
-) -> str:
- """Determine the command used to run the program, for use in help
- text. If a file or entry point was executed, the file name is
- returned. If ``python -m`` was used to execute a module or package,
- ``python -m name`` is returned.
-
- This doesn't try to be too precise, the goal is to give a concise
- name for help text. Files are only shown as their name without the
- path. ``python`` is only shown for modules, and the full path to
- ``sys.executable`` is not shown.
-
- :param path: The Python file being executed. Python puts this in
- ``sys.argv[0]``, which is used by default.
- :param _main: The ``__main__`` module. This should only be passed
- during internal testing.
-
- .. versionadded:: 8.0
- Based on command args detection in the Werkzeug reloader.
-
- :meta private:
- """
- if _main is None:
- _main = sys.modules["__main__"]
-
- if not path:
- path = sys.argv[0]
-
- # The value of __package__ indicates how Python was called. It may
- # not exist if a setuptools script is installed as an egg. It may be
- # set incorrectly for entry points created with pip on Windows.
- # It is set to "" inside a Shiv or PEX zipapp.
- if getattr(_main, "__package__", None) in {None, ""} or (
- os.name == "nt"
- and _main.__package__ == ""
- and not os.path.exists(path)
- and os.path.exists(f"{path}.exe")
- ):
- # Executed a file, like "python app.py".
- return os.path.basename(path)
-
- # Executed a module, like "python -m example".
- # Rewritten by Python from "-m script" to "/path/to/script.py".
- # Need to look at main module to determine how it was executed.
- py_module = t.cast(str, _main.__package__)
- name = os.path.splitext(os.path.basename(path))[0]
-
- # A submodule like "example.cli".
- if name != "__main__":
- py_module = f"{py_module}.{name}"
-
- return f"python -m {py_module.lstrip('.')}"
-
-
-def _expand_args(
- args: cabc.Iterable[str],
- *,
- user: bool = True,
- env: bool = True,
- glob_recursive: bool = True,
-) -> list[str]:
- """Simulate Unix shell expansion with Python functions.
-
- See :func:`glob.glob`, :func:`os.path.expanduser`, and
- :func:`os.path.expandvars`.
-
- This is intended for use on Windows, where the shell does not do any
- expansion. It may not exactly match what a Unix shell would do.
-
- :param args: List of command line arguments to expand.
- :param user: Expand user home directory.
- :param env: Expand environment variables.
- :param glob_recursive: ``**`` matches directories recursively.
-
- .. versionchanged:: 8.1
- Invalid glob patterns are treated as empty expansions rather
- than raising an error.
-
- .. versionadded:: 8.0
-
- :meta private:
- """
- from glob import glob
-
- out = []
-
- for arg in args:
- if user:
- arg = os.path.expanduser(arg)
-
- if env:
- arg = os.path.expandvars(arg)
-
- try:
- matches = glob(arg, recursive=glob_recursive)
- except re.error:
- matches = []
-
- if not matches:
- out.append(arg)
- else:
- out.extend(matches)
-
- return out
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__init__.py b/backend/.venv/lib/python3.12/site-packages/dotenv/__init__.py
deleted file mode 100644
index dde24a0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/__init__.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from typing import Any, Optional
-
-from .main import dotenv_values, find_dotenv, get_key, load_dotenv, set_key, unset_key
-
-
-def load_ipython_extension(ipython: Any) -> None:
- from .ipython import load_ipython_extension
-
- load_ipython_extension(ipython)
-
-
-def get_cli_string(
- path: Optional[str] = None,
- action: Optional[str] = None,
- key: Optional[str] = None,
- value: Optional[str] = None,
- quote: Optional[str] = None,
-):
- """Returns a string suitable for running as a shell script.
-
- Useful for converting a arguments passed to a fabric task
- to be passed to a `local` or `run` command.
- """
- command = ["dotenv"]
- if quote:
- command.append(f"-q {quote}")
- if path:
- command.append(f"-f {path}")
- if action:
- command.append(action)
- if key:
- command.append(key)
- if value:
- if " " in value:
- command.append(f'"{value}"')
- else:
- command.append(value)
-
- return " ".join(command).strip()
-
-
-__all__ = [
- "get_cli_string",
- "load_dotenv",
- "dotenv_values",
- "get_key",
- "set_key",
- "unset_key",
- "find_dotenv",
- "load_ipython_extension",
-]
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__main__.py b/backend/.venv/lib/python3.12/site-packages/dotenv/__main__.py
deleted file mode 100644
index 3977f55..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/__main__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Entry point for cli, enables execution with `python -m dotenv`"""
-
-from .cli import cli
-
-if __name__ == "__main__":
- cli()
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 7152a09..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__main__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__main__.cpython-312.pyc
deleted file mode 100644
index 3d495f6..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/__main__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/cli.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/cli.cpython-312.pyc
deleted file mode 100644
index a14c968..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/cli.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/ipython.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/ipython.cpython-312.pyc
deleted file mode 100644
index a24c6ef..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/ipython.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc
deleted file mode 100644
index 34a7687..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc
deleted file mode 100644
index 241287e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc
deleted file mode 100644
index 2093ee9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/version.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/version.cpython-312.pyc
deleted file mode 100644
index 1ce4add..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/dotenv/__pycache__/version.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/cli.py b/backend/.venv/lib/python3.12/site-packages/dotenv/cli.py
deleted file mode 100644
index c548aa3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/cli.py
+++ /dev/null
@@ -1,218 +0,0 @@
-import json
-import os
-import shlex
-import sys
-from contextlib import contextmanager
-from typing import IO, Any, Dict, Iterator, List, Optional
-
-if sys.platform == "win32":
- from subprocess import Popen
-
-try:
- import click
-except ImportError:
- sys.stderr.write(
- "It seems python-dotenv is not installed with cli option. \n"
- 'Run pip install "python-dotenv[cli]" to fix this.'
- )
- sys.exit(1)
-
-from .main import dotenv_values, set_key, unset_key
-from .version import __version__
-
-
-def enumerate_env() -> Optional[str]:
- """
- Return a path for the ${pwd}/.env file.
-
- If pwd does not exist, return None.
- """
- try:
- cwd = os.getcwd()
- except FileNotFoundError:
- return None
- path = os.path.join(cwd, ".env")
- return path
-
-
-@click.group()
-@click.option(
- "-f",
- "--file",
- default=enumerate_env(),
- type=click.Path(file_okay=True),
- help="Location of the .env file, defaults to .env file in current working directory.",
-)
-@click.option(
- "-q",
- "--quote",
- default="always",
- type=click.Choice(["always", "never", "auto"]),
- help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.",
-)
-@click.option(
- "-e",
- "--export",
- default=False,
- type=click.BOOL,
- help="Whether to write the dot file as an executable bash script.",
-)
-@click.version_option(version=__version__)
-@click.pass_context
-def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None:
- """This script is used to set, get or unset values from a .env file."""
- ctx.obj = {"QUOTE": quote, "EXPORT": export, "FILE": file}
-
-
-@contextmanager
-def stream_file(path: os.PathLike) -> Iterator[IO[str]]:
- """
- Open a file and yield the corresponding (decoded) stream.
-
- Exits with error code 2 if the file cannot be opened.
- """
-
- try:
- with open(path) as stream:
- yield stream
- except OSError as exc:
- print(f"Error opening env file: {exc}", file=sys.stderr)
- sys.exit(2)
-
-
-@cli.command(name="list")
-@click.pass_context
-@click.option(
- "--format",
- "output_format",
- default="simple",
- type=click.Choice(["simple", "json", "shell", "export"]),
- help="The format in which to display the list. Default format is simple, "
- "which displays name=value without quotes.",
-)
-def list_values(ctx: click.Context, output_format: str) -> None:
- """Display all the stored key/value."""
- file = ctx.obj["FILE"]
-
- with stream_file(file) as stream:
- values = dotenv_values(stream=stream)
-
- if output_format == "json":
- click.echo(json.dumps(values, indent=2, sort_keys=True))
- else:
- prefix = "export " if output_format == "export" else ""
- for k in sorted(values):
- v = values[k]
- if v is not None:
- if output_format in ("export", "shell"):
- v = shlex.quote(v)
- click.echo(f"{prefix}{k}={v}")
-
-
-@cli.command(name="set")
-@click.pass_context
-@click.argument("key", required=True)
-@click.argument("value", required=True)
-def set_value(ctx: click.Context, key: Any, value: Any) -> None:
- """Store the given key/value."""
- file = ctx.obj["FILE"]
- quote = ctx.obj["QUOTE"]
- export = ctx.obj["EXPORT"]
- success, key, value = set_key(file, key, value, quote, export)
- if success:
- click.echo(f"{key}={value}")
- else:
- sys.exit(1)
-
-
-@cli.command()
-@click.pass_context
-@click.argument("key", required=True)
-def get(ctx: click.Context, key: Any) -> None:
- """Retrieve the value for the given key."""
- file = ctx.obj["FILE"]
-
- with stream_file(file) as stream:
- values = dotenv_values(stream=stream)
-
- stored_value = values.get(key)
- if stored_value:
- click.echo(stored_value)
- else:
- sys.exit(1)
-
-
-@cli.command()
-@click.pass_context
-@click.argument("key", required=True)
-def unset(ctx: click.Context, key: Any) -> None:
- """Removes the given key."""
- file = ctx.obj["FILE"]
- quote = ctx.obj["QUOTE"]
- success, key = unset_key(file, key, quote)
- if success:
- click.echo(f"Successfully removed {key}")
- else:
- sys.exit(1)
-
-
-@cli.command(context_settings={"ignore_unknown_options": True})
-@click.pass_context
-@click.option(
- "--override/--no-override",
- default=True,
- help="Override variables from the environment file with those from the .env file.",
-)
-@click.argument("commandline", nargs=-1, type=click.UNPROCESSED)
-def run(ctx: click.Context, override: bool, commandline: List[str]) -> None:
- """Run command with environment variables present."""
- file = ctx.obj["FILE"]
- if not os.path.isfile(file):
- raise click.BadParameter(
- f"Invalid value for '-f' \"{file}\" does not exist.", ctx=ctx
- )
- dotenv_as_dict = {
- k: v
- for (k, v) in dotenv_values(file).items()
- if v is not None and (override or k not in os.environ)
- }
-
- if not commandline:
- click.echo("No command given.")
- sys.exit(1)
- run_command(commandline, dotenv_as_dict)
-
-
-def run_command(command: List[str], env: Dict[str, str]) -> None:
- """Replace the current process with the specified command.
-
- Replaces the current process with the specified command and the variables from `env`
- added in the current environment variables.
-
- Parameters
- ----------
- command: List[str]
- The command and it's parameters
- env: Dict
- The additional environment variables
-
- Returns
- -------
- None
- This function does not return any value. It replaces the current process with the new one.
-
- """
- # copy the current environment variables and add the vales from
- # `env`
- cmd_env = os.environ.copy()
- cmd_env.update(env)
-
- if sys.platform == "win32":
- # execvpe on Windows returns control immediately
- # rather than once the command has finished.
- p = Popen(command, universal_newlines=True, bufsize=0, shell=False, env=cmd_env)
- _, _ = p.communicate()
-
- sys.exit(p.returncode)
- else:
- os.execvpe(command[0], args=command, env=cmd_env)
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/ipython.py b/backend/.venv/lib/python3.12/site-packages/dotenv/ipython.py
deleted file mode 100644
index 4e7edbb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/ipython.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from IPython.core.magic import Magics, line_magic, magics_class # type: ignore
-from IPython.core.magic_arguments import (
- argument,
- magic_arguments,
- parse_argstring,
-) # type: ignore
-
-from .main import find_dotenv, load_dotenv
-
-
-@magics_class
-class IPythonDotEnv(Magics):
- @magic_arguments()
- @argument(
- "-o",
- "--override",
- action="store_true",
- help="Indicate to override existing variables",
- )
- @argument(
- "-v",
- "--verbose",
- action="store_true",
- help="Indicate function calls to be verbose",
- )
- @argument(
- "dotenv_path",
- nargs="?",
- type=str,
- default=".env",
- help="Search in increasingly higher folders for the `dotenv_path`",
- )
- @line_magic
- def dotenv(self, line):
- args = parse_argstring(self.dotenv, line)
- # Locate the .env file
- dotenv_path = args.dotenv_path
- try:
- dotenv_path = find_dotenv(dotenv_path, True, True)
- except IOError:
- print("cannot find .env file")
- return
-
- # Load the .env file
- load_dotenv(dotenv_path, verbose=args.verbose, override=args.override)
-
-
-def load_ipython_extension(ipython):
- """Register the %dotenv magic."""
- ipython.register_magics(IPythonDotEnv)
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/main.py b/backend/.venv/lib/python3.12/site-packages/dotenv/main.py
deleted file mode 100644
index 1d6bf0b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/main.py
+++ /dev/null
@@ -1,435 +0,0 @@
-import io
-import logging
-import os
-import pathlib
-import shutil
-import stat
-import sys
-import tempfile
-from collections import OrderedDict
-from contextlib import contextmanager
-from typing import IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, Union
-
-from .parser import Binding, parse_stream
-from .variables import parse_variables
-
-# A type alias for a string path to be used for the paths in this file.
-# These paths may flow to `open()` and `shutil.move()`; `shutil.move()`
-# only accepts string paths, not byte paths or file descriptors. See
-# https://github.com/python/typeshed/pull/6832.
-StrPath = Union[str, "os.PathLike[str]"]
-
-logger = logging.getLogger(__name__)
-
-
-def _load_dotenv_disabled() -> bool:
- """
- Determine if dotenv loading has been disabled.
- """
- if "PYTHON_DOTENV_DISABLED" not in os.environ:
- return False
- value = os.environ["PYTHON_DOTENV_DISABLED"].casefold()
- return value in {"1", "true", "t", "yes", "y"}
-
-
-def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]:
- for mapping in mappings:
- if mapping.error:
- logger.warning(
- "python-dotenv could not parse statement starting at line %s",
- mapping.original.line,
- )
- yield mapping
-
-
-class DotEnv:
- def __init__(
- self,
- dotenv_path: Optional[StrPath],
- stream: Optional[IO[str]] = None,
- verbose: bool = False,
- encoding: Optional[str] = None,
- interpolate: bool = True,
- override: bool = True,
- ) -> None:
- self.dotenv_path: Optional[StrPath] = dotenv_path
- self.stream: Optional[IO[str]] = stream
- self._dict: Optional[Dict[str, Optional[str]]] = None
- self.verbose: bool = verbose
- self.encoding: Optional[str] = encoding
- self.interpolate: bool = interpolate
- self.override: bool = override
-
- @contextmanager
- def _get_stream(self) -> Iterator[IO[str]]:
- if self.dotenv_path and _is_file_or_fifo(self.dotenv_path):
- with open(self.dotenv_path, encoding=self.encoding) as stream:
- yield stream
- elif self.stream is not None:
- yield self.stream
- else:
- if self.verbose:
- logger.info(
- "python-dotenv could not find configuration file %s.",
- self.dotenv_path or ".env",
- )
- yield io.StringIO("")
-
- def dict(self) -> Dict[str, Optional[str]]:
- """Return dotenv as dict"""
- if self._dict:
- return self._dict
-
- raw_values = self.parse()
-
- if self.interpolate:
- self._dict = OrderedDict(
- resolve_variables(raw_values, override=self.override)
- )
- else:
- self._dict = OrderedDict(raw_values)
-
- return self._dict
-
- def parse(self) -> Iterator[Tuple[str, Optional[str]]]:
- with self._get_stream() as stream:
- for mapping in with_warn_for_invalid_lines(parse_stream(stream)):
- if mapping.key is not None:
- yield mapping.key, mapping.value
-
- def set_as_environment_variables(self) -> bool:
- """
- Load the current dotenv as system environment variable.
- """
- if not self.dict():
- return False
-
- for k, v in self.dict().items():
- if k in os.environ and not self.override:
- continue
- if v is not None:
- os.environ[k] = v
-
- return True
-
- def get(self, key: str) -> Optional[str]:
- """ """
- data = self.dict()
-
- if key in data:
- return data[key]
-
- if self.verbose:
- logger.warning("Key %s not found in %s.", key, self.dotenv_path)
-
- return None
-
-
-def get_key(
- dotenv_path: StrPath,
- key_to_get: str,
- encoding: Optional[str] = "utf-8",
-) -> Optional[str]:
- """
- Get the value of a given key from the given .env.
-
- Returns `None` if the key isn't found or doesn't have a value.
- """
- return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get)
-
-
-@contextmanager
-def rewrite(
- path: StrPath,
- encoding: Optional[str],
-) -> Iterator[Tuple[IO[str], IO[str]]]:
- pathlib.Path(path).touch()
-
- with tempfile.NamedTemporaryFile(mode="w", encoding=encoding, delete=False) as dest:
- error = None
- try:
- with open(path, encoding=encoding) as source:
- yield (source, dest)
- except BaseException as err:
- error = err
-
- if error is None:
- shutil.move(dest.name, path)
- else:
- os.unlink(dest.name)
- raise error from None
-
-
-def set_key(
- dotenv_path: StrPath,
- key_to_set: str,
- value_to_set: str,
- quote_mode: str = "always",
- export: bool = False,
- encoding: Optional[str] = "utf-8",
-) -> Tuple[Optional[bool], str, str]:
- """
- Adds or Updates a key/value to the given .env
-
- If the .env path given doesn't exist, fails instead of risking creating
- an orphan .env somewhere in the filesystem
- """
- if quote_mode not in ("always", "auto", "never"):
- raise ValueError(f"Unknown quote_mode: {quote_mode}")
-
- quote = quote_mode == "always" or (
- quote_mode == "auto" and not value_to_set.isalnum()
- )
-
- if quote:
- value_out = "'{}'".format(value_to_set.replace("'", "\\'"))
- else:
- value_out = value_to_set
- if export:
- line_out = f"export {key_to_set}={value_out}\n"
- else:
- line_out = f"{key_to_set}={value_out}\n"
-
- with rewrite(dotenv_path, encoding=encoding) as (source, dest):
- replaced = False
- missing_newline = False
- for mapping in with_warn_for_invalid_lines(parse_stream(source)):
- if mapping.key == key_to_set:
- dest.write(line_out)
- replaced = True
- else:
- dest.write(mapping.original.string)
- missing_newline = not mapping.original.string.endswith("\n")
- if not replaced:
- if missing_newline:
- dest.write("\n")
- dest.write(line_out)
-
- return True, key_to_set, value_to_set
-
-
-def unset_key(
- dotenv_path: StrPath,
- key_to_unset: str,
- quote_mode: str = "always",
- encoding: Optional[str] = "utf-8",
-) -> Tuple[Optional[bool], str]:
- """
- Removes a given key from the given `.env` file.
-
- If the .env path given doesn't exist, fails.
- If the given key doesn't exist in the .env, fails.
- """
- if not os.path.exists(dotenv_path):
- logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path)
- return None, key_to_unset
-
- removed = False
- with rewrite(dotenv_path, encoding=encoding) as (source, dest):
- for mapping in with_warn_for_invalid_lines(parse_stream(source)):
- if mapping.key == key_to_unset:
- removed = True
- else:
- dest.write(mapping.original.string)
-
- if not removed:
- logger.warning(
- "Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path
- )
- return None, key_to_unset
-
- return removed, key_to_unset
-
-
-def resolve_variables(
- values: Iterable[Tuple[str, Optional[str]]],
- override: bool,
-) -> Mapping[str, Optional[str]]:
- new_values: Dict[str, Optional[str]] = {}
-
- for name, value in values:
- if value is None:
- result = None
- else:
- atoms = parse_variables(value)
- env: Dict[str, Optional[str]] = {}
- if override:
- env.update(os.environ) # type: ignore
- env.update(new_values)
- else:
- env.update(new_values)
- env.update(os.environ) # type: ignore
- result = "".join(atom.resolve(env) for atom in atoms)
-
- new_values[name] = result
-
- return new_values
-
-
-def _walk_to_root(path: str) -> Iterator[str]:
- """
- Yield directories starting from the given directory up to the root
- """
- if not os.path.exists(path):
- raise IOError("Starting path not found")
-
- if os.path.isfile(path):
- path = os.path.dirname(path)
-
- last_dir = None
- current_dir = os.path.abspath(path)
- while last_dir != current_dir:
- yield current_dir
- parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
- last_dir, current_dir = current_dir, parent_dir
-
-
-def find_dotenv(
- filename: str = ".env",
- raise_error_if_not_found: bool = False,
- usecwd: bool = False,
-) -> str:
- """
- Search in increasingly higher folders for the given file
-
- Returns path to the file if found, or an empty string otherwise
- """
-
- def _is_interactive():
- """Decide whether this is running in a REPL or IPython notebook"""
- if hasattr(sys, "ps1") or hasattr(sys, "ps2"):
- return True
- try:
- main = __import__("__main__", None, None, fromlist=["__file__"])
- except ModuleNotFoundError:
- return False
- return not hasattr(main, "__file__")
-
- def _is_debugger():
- return sys.gettrace() is not None
-
- if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False):
- # Should work without __file__, e.g. in REPL or IPython notebook.
- path = os.getcwd()
- else:
- # will work for .py files
- frame = sys._getframe()
- current_file = __file__
-
- while frame.f_code.co_filename == current_file or not os.path.exists(
- frame.f_code.co_filename
- ):
- assert frame.f_back is not None
- frame = frame.f_back
- frame_filename = frame.f_code.co_filename
- path = os.path.dirname(os.path.abspath(frame_filename))
-
- for dirname in _walk_to_root(path):
- check_path = os.path.join(dirname, filename)
- if _is_file_or_fifo(check_path):
- return check_path
-
- if raise_error_if_not_found:
- raise IOError("File not found")
-
- return ""
-
-
-def load_dotenv(
- dotenv_path: Optional[StrPath] = None,
- stream: Optional[IO[str]] = None,
- verbose: bool = False,
- override: bool = False,
- interpolate: bool = True,
- encoding: Optional[str] = "utf-8",
-) -> bool:
- """Parse a .env file and then load all the variables found as environment variables.
-
- Parameters:
- dotenv_path: Absolute or relative path to .env file.
- stream: Text stream (such as `io.StringIO`) with .env content, used if
- `dotenv_path` is `None`.
- verbose: Whether to output a warning the .env file is missing.
- override: Whether to override the system environment variables with the variables
- from the `.env` file.
- encoding: Encoding to be used to read the file.
- Returns:
- Bool: True if at least one environment variable is set else False
-
- If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
- .env file with it's default parameters. If you need to change the default parameters
- of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result
- to this function as `dotenv_path`.
-
- If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value,
- .env loading is disabled.
- """
- if _load_dotenv_disabled():
- logger.debug(
- "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
- )
- return False
-
- if dotenv_path is None and stream is None:
- dotenv_path = find_dotenv()
-
- dotenv = DotEnv(
- dotenv_path=dotenv_path,
- stream=stream,
- verbose=verbose,
- interpolate=interpolate,
- override=override,
- encoding=encoding,
- )
- return dotenv.set_as_environment_variables()
-
-
-def dotenv_values(
- dotenv_path: Optional[StrPath] = None,
- stream: Optional[IO[str]] = None,
- verbose: bool = False,
- interpolate: bool = True,
- encoding: Optional[str] = "utf-8",
-) -> Dict[str, Optional[str]]:
- """
- Parse a .env file and return its content as a dict.
-
- The returned dict will have `None` values for keys without values in the .env file.
- For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in
- `{"foo": None}`
-
- Parameters:
- dotenv_path: Absolute or relative path to the .env file.
- stream: `StringIO` object with .env content, used if `dotenv_path` is `None`.
- verbose: Whether to output a warning if the .env file is missing.
- encoding: Encoding to be used to read the file.
-
- If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
- .env file.
- """
- if dotenv_path is None and stream is None:
- dotenv_path = find_dotenv()
-
- return DotEnv(
- dotenv_path=dotenv_path,
- stream=stream,
- verbose=verbose,
- interpolate=interpolate,
- override=True,
- encoding=encoding,
- ).dict()
-
-
-def _is_file_or_fifo(path: StrPath) -> bool:
- """
- Return True if `path` exists and is either a regular file or a FIFO.
- """
- if os.path.isfile(path):
- return True
-
- try:
- st = os.stat(path)
- except (FileNotFoundError, OSError):
- return False
-
- return stat.S_ISFIFO(st.st_mode)
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/parser.py b/backend/.venv/lib/python3.12/site-packages/dotenv/parser.py
deleted file mode 100644
index eb100b4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/parser.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import codecs
-import re
-from typing import (
- IO,
- Iterator,
- Match,
- NamedTuple,
- Optional,
- Pattern,
- Sequence,
-)
-
-
-def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]:
- return re.compile(string, re.UNICODE | extra_flags)
-
-
-_newline = make_regex(r"(\r\n|\n|\r)")
-_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE)
-_whitespace = make_regex(r"[^\S\r\n]*")
-_export = make_regex(r"(?:export[^\S\r\n]+)?")
-_single_quoted_key = make_regex(r"'([^']+)'")
-_unquoted_key = make_regex(r"([^=\#\s]+)")
-_equal_sign = make_regex(r"(=[^\S\r\n]*)")
-_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'")
-_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"')
-_unquoted_value = make_regex(r"([^\r\n]*)")
-_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?")
-_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)")
-_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?")
-_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]")
-_single_quote_escapes = make_regex(r"\\[\\']")
-
-
-class Original(NamedTuple):
- string: str
- line: int
-
-
-class Binding(NamedTuple):
- key: Optional[str]
- value: Optional[str]
- original: Original
- error: bool
-
-
-class Position:
- def __init__(self, chars: int, line: int) -> None:
- self.chars = chars
- self.line = line
-
- @classmethod
- def start(cls) -> "Position":
- return cls(chars=0, line=1)
-
- def set(self, other: "Position") -> None:
- self.chars = other.chars
- self.line = other.line
-
- def advance(self, string: str) -> None:
- self.chars += len(string)
- self.line += len(re.findall(_newline, string))
-
-
-class Error(Exception):
- pass
-
-
-class Reader:
- def __init__(self, stream: IO[str]) -> None:
- self.string = stream.read()
- self.position = Position.start()
- self.mark = Position.start()
-
- def has_next(self) -> bool:
- return self.position.chars < len(self.string)
-
- def set_mark(self) -> None:
- self.mark.set(self.position)
-
- def get_marked(self) -> Original:
- return Original(
- string=self.string[self.mark.chars : self.position.chars],
- line=self.mark.line,
- )
-
- def peek(self, count: int) -> str:
- return self.string[self.position.chars : self.position.chars + count]
-
- def read(self, count: int) -> str:
- result = self.string[self.position.chars : self.position.chars + count]
- if len(result) < count:
- raise Error("read: End of string")
- self.position.advance(result)
- return result
-
- def read_regex(self, regex: Pattern[str]) -> Sequence[str]:
- match = regex.match(self.string, self.position.chars)
- if match is None:
- raise Error("read_regex: Pattern not found")
- self.position.advance(self.string[match.start() : match.end()])
- return match.groups()
-
-
-def decode_escapes(regex: Pattern[str], string: str) -> str:
- def decode_match(match: Match[str]) -> str:
- return codecs.decode(match.group(0), "unicode-escape") # type: ignore
-
- return regex.sub(decode_match, string)
-
-
-def parse_key(reader: Reader) -> Optional[str]:
- char = reader.peek(1)
- if char == "#":
- return None
- elif char == "'":
- (key,) = reader.read_regex(_single_quoted_key)
- else:
- (key,) = reader.read_regex(_unquoted_key)
- return key
-
-
-def parse_unquoted_value(reader: Reader) -> str:
- (part,) = reader.read_regex(_unquoted_value)
- return re.sub(r"\s+#.*", "", part).rstrip()
-
-
-def parse_value(reader: Reader) -> str:
- char = reader.peek(1)
- if char == "'":
- (value,) = reader.read_regex(_single_quoted_value)
- return decode_escapes(_single_quote_escapes, value)
- elif char == '"':
- (value,) = reader.read_regex(_double_quoted_value)
- return decode_escapes(_double_quote_escapes, value)
- elif char in ("", "\n", "\r"):
- return ""
- else:
- return parse_unquoted_value(reader)
-
-
-def parse_binding(reader: Reader) -> Binding:
- reader.set_mark()
- try:
- reader.read_regex(_multiline_whitespace)
- if not reader.has_next():
- return Binding(
- key=None,
- value=None,
- original=reader.get_marked(),
- error=False,
- )
- reader.read_regex(_export)
- key = parse_key(reader)
- reader.read_regex(_whitespace)
- if reader.peek(1) == "=":
- reader.read_regex(_equal_sign)
- value: Optional[str] = parse_value(reader)
- else:
- value = None
- reader.read_regex(_comment)
- reader.read_regex(_end_of_line)
- return Binding(
- key=key,
- value=value,
- original=reader.get_marked(),
- error=False,
- )
- except Error:
- reader.read_regex(_rest_of_line)
- return Binding(
- key=None,
- value=None,
- original=reader.get_marked(),
- error=True,
- )
-
-
-def parse_stream(stream: IO[str]) -> Iterator[Binding]:
- reader = Reader(stream)
- while reader.has_next():
- yield parse_binding(reader)
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/py.typed b/backend/.venv/lib/python3.12/site-packages/dotenv/py.typed
deleted file mode 100644
index 7632ecf..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/py.typed
+++ /dev/null
@@ -1 +0,0 @@
-# Marker file for PEP 561
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/variables.py b/backend/.venv/lib/python3.12/site-packages/dotenv/variables.py
deleted file mode 100644
index 667f2f2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/variables.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import re
-from abc import ABCMeta, abstractmethod
-from typing import Iterator, Mapping, Optional, Pattern
-
-_posix_variable: Pattern[str] = re.compile(
- r"""
- \$\{
- (?P[^\}:]*)
- (?::-
- (?P[^\}]*)
- )?
- \}
- """,
- re.VERBOSE,
-)
-
-
-class Atom(metaclass=ABCMeta):
- def __ne__(self, other: object) -> bool:
- result = self.__eq__(other)
- if result is NotImplemented:
- return NotImplemented
- return not result
-
- @abstractmethod
- def resolve(self, env: Mapping[str, Optional[str]]) -> str: ...
-
-
-class Literal(Atom):
- def __init__(self, value: str) -> None:
- self.value = value
-
- def __repr__(self) -> str:
- return f"Literal(value={self.value})"
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.value == other.value
-
- def __hash__(self) -> int:
- return hash((self.__class__, self.value))
-
- def resolve(self, env: Mapping[str, Optional[str]]) -> str:
- return self.value
-
-
-class Variable(Atom):
- def __init__(self, name: str, default: Optional[str]) -> None:
- self.name = name
- self.default = default
-
- def __repr__(self) -> str:
- return f"Variable(name={self.name}, default={self.default})"
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, self.__class__):
- return NotImplemented
- return (self.name, self.default) == (other.name, other.default)
-
- def __hash__(self) -> int:
- return hash((self.__class__, self.name, self.default))
-
- def resolve(self, env: Mapping[str, Optional[str]]) -> str:
- default = self.default if self.default is not None else ""
- result = env.get(self.name, default)
- return result if result is not None else ""
-
-
-def parse_variables(value: str) -> Iterator[Atom]:
- cursor = 0
-
- for match in _posix_variable.finditer(value):
- (start, end) = match.span()
- name = match["name"]
- default = match["default"]
-
- if start > cursor:
- yield Literal(value=value[cursor:start])
-
- yield Variable(name=name, default=default)
- cursor = end
-
- length = len(value)
- if cursor < length:
- yield Literal(value=value[cursor:length])
diff --git a/backend/.venv/lib/python3.12/site-packages/dotenv/version.py b/backend/.venv/lib/python3.12/site-packages/dotenv/version.py
deleted file mode 100644
index a955fda..0000000
--- a/backend/.venv/lib/python3.12/site-packages/dotenv/version.py
+++ /dev/null
@@ -1 +0,0 @@
-__version__ = "1.2.1"
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/METADATA
deleted file mode 100644
index f5f8bd9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/METADATA
+++ /dev/null
@@ -1,645 +0,0 @@
-Metadata-Version: 2.4
-Name: fastapi
-Version: 0.128.0
-Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
-Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?=
-License-Expression: MIT
-License-File: LICENSE
-Classifier: Intended Audience :: Information Technology
-Classifier: Intended Audience :: System Administrators
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python
-Classifier: Topic :: Internet
-Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Software Development :: Libraries
-Classifier: Topic :: Software Development
-Classifier: Typing :: Typed
-Classifier: Development Status :: 4 - Beta
-Classifier: Environment :: Web Environment
-Classifier: Framework :: AsyncIO
-Classifier: Framework :: FastAPI
-Classifier: Framework :: Pydantic
-Classifier: Framework :: Pydantic :: 2
-Classifier: Intended Audience :: Developers
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: 3.14
-Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
-Classifier: Topic :: Internet :: WWW/HTTP
-Project-URL: Homepage, https://github.com/fastapi/fastapi
-Project-URL: Documentation, https://fastapi.tiangolo.com/
-Project-URL: Repository, https://github.com/fastapi/fastapi
-Project-URL: Issues, https://github.com/fastapi/fastapi/issues
-Project-URL: Changelog, https://fastapi.tiangolo.com/release-notes/
-Requires-Python: >=3.9
-Requires-Dist: starlette<0.51.0,>=0.40.0
-Requires-Dist: pydantic>=2.7.0
-Requires-Dist: typing-extensions>=4.8.0
-Requires-Dist: annotated-doc>=0.0.2
-Provides-Extra: standard
-Requires-Dist: fastapi-cli[standard]>=0.0.8; extra == "standard"
-Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "standard"
-Requires-Dist: jinja2>=3.1.5; extra == "standard"
-Requires-Dist: python-multipart>=0.0.18; extra == "standard"
-Requires-Dist: email-validator>=2.0.0; extra == "standard"
-Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard"
-Requires-Dist: pydantic-settings>=2.0.0; extra == "standard"
-Requires-Dist: pydantic-extra-types>=2.0.0; extra == "standard"
-Provides-Extra: standard-no-fastapi-cloud-cli
-Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]>=0.0.8; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: jinja2>=3.1.5; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: python-multipart>=0.0.18; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: email-validator>=2.0.0; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: pydantic-settings>=2.0.0; extra == "standard-no-fastapi-cloud-cli"
-Requires-Dist: pydantic-extra-types>=2.0.0; extra == "standard-no-fastapi-cloud-cli"
-Provides-Extra: all
-Requires-Dist: fastapi-cli[standard]>=0.0.8; extra == "all"
-Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "all"
-Requires-Dist: jinja2>=3.1.5; extra == "all"
-Requires-Dist: python-multipart>=0.0.18; extra == "all"
-Requires-Dist: itsdangerous>=1.1.0; extra == "all"
-Requires-Dist: pyyaml>=5.3.1; extra == "all"
-Requires-Dist: ujson!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,>=4.0.1; extra == "all"
-Requires-Dist: orjson>=3.2.1; extra == "all"
-Requires-Dist: email-validator>=2.0.0; extra == "all"
-Requires-Dist: uvicorn[standard]>=0.12.0; extra == "all"
-Requires-Dist: pydantic-settings>=2.0.0; extra == "all"
-Requires-Dist: pydantic-extra-types>=2.0.0; extra == "all"
-Description-Content-Type: text/markdown
-
-
-
-
-
- FastAPI framework, high performance, easy to learn, fast to code, ready for production
-
-
----
-
-**Documentation**: https://fastapi.tiangolo.com
-
-**Source Code**: https://github.com/fastapi/fastapi
-
----
-
-FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
-
-The key features are:
-
-* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
-* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
-* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
-* **Intuitive**: Great editor support. Completion everywhere. Less time debugging.
-* **Easy**: Designed to be easy to use and learn. Less time reading docs.
-* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
-* **Robust**: Get production-ready code. With automatic interactive documentation.
-* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
-
-* estimation based on tests conducted by an internal development team, building production applications.
-
-## Sponsors
-
-
-### Keystone Sponsor
-
-
-
-### Gold and Silver Sponsors
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Other sponsors
-
-## Opinions
-
-"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
-
-
-
----
-
-"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
-
-
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber(ref)
-
----
-
-"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_"
-
-
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix(ref)
-
----
-
-"_I’m over the moon excited about **FastAPI**. It’s so fun!_"
-
-
-
----
-
-"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._"
-
-
-
----
-
-"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_"
-
-"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_"
-
-
-
----
-
-"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._"
-
-
-
----
-
-## FastAPI mini documentary
-
-There's a FastAPI mini documentary released at the end of 2025, you can watch it online:
-
-
-
-## **Typer**, the FastAPI of CLIs
-
-
-
-If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**.
-
-**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
-
-## Requirements
-
-FastAPI stands on the shoulders of giants:
-
-* Starlette for the web parts.
-* Pydantic for the data parts.
-
-## Installation
-
-Create and activate a virtual environment and then install FastAPI:
-
-
-
-**Note**: Make sure you put `"fastapi[standard]"` in quotes to ensure it works in all terminals.
-
-## Example
-
-### Create it
-
-Create a file `main.py` with:
-
-```Python
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-```
-
-
-Or use async def...
-
-If your code uses `async` / `await`, use `async def`:
-
-```Python hl_lines="9 14"
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-async def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-async def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-```
-
-**Note**:
-
-If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs.
-
-
-
-### Run it
-
-Run the server with:
-
-
-
-```console
-$ fastapi dev main.py
-
- ╭────────── FastAPI CLI - Development mode ───────────╮
- │ │
- │ Serving at: http://127.0.0.1:8000 │
- │ │
- │ API docs: http://127.0.0.1:8000/docs │
- │ │
- │ Running in development mode, for production use: │
- │ │
- │ fastapi run │
- │ │
- ╰─────────────────────────────────────────────────────╯
-
-INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
-INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
-INFO: Started reloader process [2248755] using WatchFiles
-INFO: Started server process [2248757]
-INFO: Waiting for application startup.
-INFO: Application startup complete.
-```
-
-
-
-
-About the command fastapi dev main.py...
-
-The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn.
-
-By default, `fastapi dev` will start with auto-reload enabled for local development.
-
-You can read more about it in the FastAPI CLI docs.
-
-
-
-### Check it
-
-Open your browser at http://127.0.0.1:8000/items/5?q=somequery.
-
-You will see the JSON response as:
-
-```JSON
-{"item_id": 5, "q": "somequery"}
-```
-
-You already created an API that:
-
-* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`.
-* Both _paths_ take `GET` operations (also known as HTTP _methods_).
-* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`.
-* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`.
-
-### Interactive API docs
-
-Now go to http://127.0.0.1:8000/docs.
-
-You will see the automatic interactive API documentation (provided by Swagger UI):
-
-
-
-### Alternative API docs
-
-And now, go to http://127.0.0.1:8000/redoc.
-
-You will see the alternative automatic documentation (provided by ReDoc):
-
-
-
-## Example upgrade
-
-Now modify the file `main.py` to receive a body from a `PUT` request.
-
-Declare the body using standard Python types, thanks to Pydantic.
-
-```Python hl_lines="4 9-12 25-27"
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- price: float
- is_offer: Union[bool, None] = None
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-
-
-@app.put("/items/{item_id}")
-def update_item(item_id: int, item: Item):
- return {"item_name": item.name, "item_id": item_id}
-```
-
-The `fastapi dev` server should reload automatically.
-
-### Interactive API docs upgrade
-
-Now go to http://127.0.0.1:8000/docs.
-
-* The interactive API documentation will be automatically updated, including the new body:
-
-
-
-* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API:
-
-
-
-* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen:
-
-
-
-### Alternative API docs upgrade
-
-And now, go to http://127.0.0.1:8000/redoc.
-
-* The alternative documentation will also reflect the new query parameter and body:
-
-
-
-### Recap
-
-In summary, you declare **once** the types of parameters, body, etc. as function parameters.
-
-You do that with standard modern Python types.
-
-You don't have to learn a new syntax, the methods or classes of a specific library, etc.
-
-Just standard **Python**.
-
-For example, for an `int`:
-
-```Python
-item_id: int
-```
-
-or for a more complex `Item` model:
-
-```Python
-item: Item
-```
-
-...and with that single declaration you get:
-
-* Editor support, including:
- * Completion.
- * Type checks.
-* Validation of data:
- * Automatic and clear errors when the data is invalid.
- * Validation even for deeply nested JSON objects.
-* Conversion of input data: coming from the network to Python data and types. Reading from:
- * JSON.
- * Path parameters.
- * Query parameters.
- * Cookies.
- * Headers.
- * Forms.
- * Files.
-* Conversion of output data: converting from Python data and types to network data (as JSON):
- * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc).
- * `datetime` objects.
- * `UUID` objects.
- * Database models.
- * ...and many more.
-* Automatic interactive API documentation, including 2 alternative user interfaces:
- * Swagger UI.
- * ReDoc.
-
----
-
-Coming back to the previous code example, **FastAPI** will:
-
-* Validate that there is an `item_id` in the path for `GET` and `PUT` requests.
-* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests.
- * If it is not, the client will see a useful, clear error.
-* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests.
- * As the `q` parameter is declared with `= None`, it is optional.
- * Without the `None` it would be required (as is the body in the case with `PUT`).
-* For `PUT` requests to `/items/{item_id}`, read the body as JSON:
- * Check that it has a required attribute `name` that should be a `str`.
- * Check that it has a required attribute `price` that has to be a `float`.
- * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
- * All this would also work for deeply nested JSON objects.
-* Convert from and to JSON automatically.
-* Document everything with OpenAPI, that can be used by:
- * Interactive documentation systems.
- * Automatic client code generation systems, for many languages.
-* Provide 2 interactive documentation web interfaces directly.
-
----
-
-We just scratched the surface, but you already get the idea of how it all works.
-
-Try changing the line with:
-
-```Python
- return {"item_name": item.name, "item_id": item_id}
-```
-
-...from:
-
-```Python
- ... "item_name": item.name ...
-```
-
-...to:
-
-```Python
- ... "item_price": item.price ...
-```
-
-...and see how your editor will auto-complete the attributes and know their types:
-
-
-
-For a more complete example including more features, see the Tutorial - User Guide.
-
-**Spoiler alert**: the tutorial - user guide includes:
-
-* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
-* How to set **validation constraints** as `maximum_length` or `regex`.
-* A very powerful and easy to use **Dependency Injection** system.
-* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
-* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).
-* **GraphQL** integration with Strawberry and other libraries.
-* Many extra features (thanks to Starlette) as:
- * **WebSockets**
- * extremely easy tests based on HTTPX and `pytest`
- * **CORS**
- * **Cookie Sessions**
- * ...and more.
-
-### Deploy your app (optional)
-
-You can optionally deploy your FastAPI app to FastAPI Cloud, go and join the waiting list if you haven't. 🚀
-
-If you already have a **FastAPI Cloud** account (we invited you from the waiting list 😉), you can deploy your application with one command.
-
-Before deploying, make sure you are logged in:
-
-
-
-```console
-$ fastapi login
-
-You are logged in to FastAPI Cloud 🚀
-```
-
-
-
-Then deploy your app:
-
-
-
-```console
-$ fastapi deploy
-
-Deploying to FastAPI Cloud...
-
-✅ Deployment successful!
-
-🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
-```
-
-
-
-That's it! Now you can access your app at that URL. ✨
-
-#### About FastAPI Cloud
-
-**FastAPI Cloud** is built by the same author and team behind **FastAPI**.
-
-It streamlines the process of **building**, **deploying**, and **accessing** an API with minimal effort.
-
-It brings the same **developer experience** of building apps with FastAPI to **deploying** them to the cloud. 🎉
-
-FastAPI Cloud is the primary sponsor and funding provider for the *FastAPI and friends* open source projects. ✨
-
-#### Deploy to other cloud providers
-
-FastAPI is open source and based on standards. You can deploy FastAPI apps to any cloud provider you choose.
-
-Follow your cloud provider's guides to deploy FastAPI apps with them. 🤓
-
-## Performance
-
-Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
-
-To understand more about it, see the section Benchmarks.
-
-## Dependencies
-
-FastAPI depends on Pydantic and Starlette.
-
-### `standard` Dependencies
-
-When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
-
-Used by Pydantic:
-
-* email-validator - for email validation.
-
-Used by Starlette:
-
-* httpx - Required if you want to use the `TestClient`.
-* jinja2 - Required if you want to use the default template configuration.
-* python-multipart - Required if you want to support form "parsing", with `request.form()`.
-
-Used by FastAPI:
-
-* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
-* `fastapi-cli[standard]` - to provide the `fastapi` command.
- * This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to FastAPI Cloud.
-
-### Without `standard` Dependencies
-
-If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`.
-
-### Without `fastapi-cloud-cli`
-
-If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
-
-### Additional Optional Dependencies
-
-There are some additional dependencies you might want to install.
-
-Additional optional Pydantic dependencies:
-
-* pydantic-settings - for settings management.
-* pydantic-extra-types - for extra types to be used with Pydantic.
-
-Additional optional FastAPI dependencies:
-
-* orjson - Required if you want to use `ORJSONResponse`.
-* ujson - Required if you want to use `UJSONResponse`.
-
-## License
-
-This project is licensed under the terms of the MIT license.
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/RECORD
deleted file mode 100644
index 372e3e4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/RECORD
+++ /dev/null
@@ -1,103 +0,0 @@
-../../../bin/fastapi,sha256=YoJmb9ZCq_mki6xo8vzXXMZGMucbJZsUkvK0Sh_J-KE,213
-fastapi-0.128.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-fastapi-0.128.0.dist-info/METADATA,sha256=hKL7LtKoBl4ojHSJ_u5MsihJRMPBU5AsppXt75m24FY,30977
-fastapi-0.128.0.dist-info/RECORD,,
-fastapi-0.128.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-fastapi-0.128.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
-fastapi-0.128.0.dist-info/entry_points.txt,sha256=GCf-WbIZxyGT4MUmrPGj1cOHYZoGsNPHAvNkT6hnGeA,61
-fastapi-0.128.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
-fastapi/__init__.py,sha256=wlYVfo3p5m4F8JxyVpV1aFS5XKRzgCXBfB9s231GveY,1081
-fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
-fastapi/__pycache__/__init__.cpython-312.pyc,,
-fastapi/__pycache__/__main__.cpython-312.pyc,,
-fastapi/__pycache__/applications.cpython-312.pyc,,
-fastapi/__pycache__/background.cpython-312.pyc,,
-fastapi/__pycache__/cli.cpython-312.pyc,,
-fastapi/__pycache__/concurrency.cpython-312.pyc,,
-fastapi/__pycache__/datastructures.cpython-312.pyc,,
-fastapi/__pycache__/encoders.cpython-312.pyc,,
-fastapi/__pycache__/exception_handlers.cpython-312.pyc,,
-fastapi/__pycache__/exceptions.cpython-312.pyc,,
-fastapi/__pycache__/logger.cpython-312.pyc,,
-fastapi/__pycache__/param_functions.cpython-312.pyc,,
-fastapi/__pycache__/params.cpython-312.pyc,,
-fastapi/__pycache__/requests.cpython-312.pyc,,
-fastapi/__pycache__/responses.cpython-312.pyc,,
-fastapi/__pycache__/routing.cpython-312.pyc,,
-fastapi/__pycache__/staticfiles.cpython-312.pyc,,
-fastapi/__pycache__/templating.cpython-312.pyc,,
-fastapi/__pycache__/testclient.cpython-312.pyc,,
-fastapi/__pycache__/types.cpython-312.pyc,,
-fastapi/__pycache__/utils.cpython-312.pyc,,
-fastapi/__pycache__/websockets.cpython-312.pyc,,
-fastapi/_compat/__init__.py,sha256=o3dg67W5LlwA52_1Y9Re_JhelcG0oj5ke_GdQHcwBnw,2226
-fastapi/_compat/__pycache__/__init__.cpython-312.pyc,,
-fastapi/_compat/__pycache__/shared.cpython-312.pyc,,
-fastapi/_compat/__pycache__/v2.cpython-312.pyc,,
-fastapi/_compat/shared.py,sha256=yFZWOnzG1JRIPuLOk0eaBcrwP2bap8UkP5I0XBuVZck,6842
-fastapi/_compat/v2.py,sha256=B5OawqkcpTaaDLNZDHFlasGQyYFS7VFbwyDVwUTydE0,19597
-fastapi/applications.py,sha256=IO5F5FdRacBFXYxGPk7zPbwRCa-cxN74HHf0eMEp7xE,180536
-fastapi/background.py,sha256=fDNVXWBZniIQIxW3v-Sc99FT2p4RDKOOWW2fhOe4Nko,1793
-fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418
-fastapi/concurrency.py,sha256=xHGDEOQAA6cvFEDX46oq3r2t1Zd4sVvreaRgdIE4juM,1489
-fastapi/datastructures.py,sha256=41qs2ZhTzORMGn7JSAF9qsiPY9XP4uGyGOMKhfzg4i4,5205
-fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-fastapi/dependencies/__pycache__/__init__.cpython-312.pyc,,
-fastapi/dependencies/__pycache__/models.cpython-312.pyc,,
-fastapi/dependencies/__pycache__/utils.cpython-312.pyc,,
-fastapi/dependencies/models.py,sha256=TjJB2l6m-vhFkau7ysLdgcymZ6SdIJmlrJjqMJs5TZc,7317
-fastapi/dependencies/utils.py,sha256=rJQFCFUC7q765yUcOoSV78GCyTGfS2ePLbMvJ2B-yLc,38549
-fastapi/encoders.py,sha256=uQfXjliV2O93wy7sng3pUuLZw9UOw8HNsAHb2B1ZfEs,11004
-fastapi/exception_handlers.py,sha256=YVcT8Zy021VYYeecgdyh5YEUjEIHKcLspbkSf4OfbJI,1275
-fastapi/exceptions.py,sha256=enNT5h_wDyzY90qA4a_VqMDRSUNHTd1LX_vihMNa-LE,6973
-fastapi/logger.py,sha256=I9NNi3ov8AcqbsbC9wl1X-hdItKgYt2XTrx1f99Zpl4,54
-fastapi/middleware/__init__.py,sha256=oQDxiFVcc1fYJUOIFvphnK7pTT5kktmfL32QXpBFvvo,58
-fastapi/middleware/__pycache__/__init__.cpython-312.pyc,,
-fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc,,
-fastapi/middleware/__pycache__/cors.cpython-312.pyc,,
-fastapi/middleware/__pycache__/gzip.cpython-312.pyc,,
-fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc,,
-fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc,,
-fastapi/middleware/__pycache__/wsgi.cpython-312.pyc,,
-fastapi/middleware/asyncexitstack.py,sha256=RKGlQpGzg3GLosqVhrxBy_NCZ9qJS7zQeNHt5Y3x-00,637
-fastapi/middleware/cors.py,sha256=ynwjWQZoc_vbhzZ3_ZXceoaSrslHFHPdoM52rXr0WUU,79
-fastapi/middleware/gzip.py,sha256=xM5PcsH8QlAimZw4VDvcmTnqQamslThsfe3CVN2voa0,79
-fastapi/middleware/httpsredirect.py,sha256=rL8eXMnmLijwVkH7_400zHri1AekfeBd6D6qs8ix950,115
-fastapi/middleware/trustedhost.py,sha256=eE5XGRxGa7c5zPnMJDGp3BxaL25k5iVQlhnv-Pk0Pss,109
-fastapi/middleware/wsgi.py,sha256=Z3Ue-7wni4lUZMvH3G9ek__acgYdJstbnpZX_HQAboY,79
-fastapi/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-fastapi/openapi/__pycache__/__init__.cpython-312.pyc,,
-fastapi/openapi/__pycache__/constants.cpython-312.pyc,,
-fastapi/openapi/__pycache__/docs.cpython-312.pyc,,
-fastapi/openapi/__pycache__/models.cpython-312.pyc,,
-fastapi/openapi/__pycache__/utils.cpython-312.pyc,,
-fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,153
-fastapi/openapi/docs.py,sha256=wqcXZOhBdnf2pilVNyAIfjKAhfH9MXaQiitwZeJCR7I,10335
-fastapi/openapi/models.py,sha256=xJfPRE7DqNvtqgdouXbtMCCLBrZ-4Bd87QaA_WPUVTA,15419
-fastapi/openapi/utils.py,sha256=HsOqZ8uWSTUYL18jhYI0gU-A1nLHCNu3k3UEIAVzRyY,23795
-fastapi/param_functions.py,sha256=O8bsr2xM8XODE0wjtev8sZLl3Tt_glpdQteM2relVrU,64466
-fastapi/params.py,sha256=YS7z57t0N4H8Rdogx1sU6R-KZDcLn5y46SGz8z5lX-s,26982
-fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142
-fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761
-fastapi/routing.py,sha256=dHZW6NLzbvD7jZT2N-V6fj1Irbe8HR-FDeiBNSuTHHs,178746
-fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881
-fastapi/security/__pycache__/__init__.cpython-312.pyc,,
-fastapi/security/__pycache__/api_key.cpython-312.pyc,,
-fastapi/security/__pycache__/base.cpython-312.pyc,,
-fastapi/security/__pycache__/http.cpython-312.pyc,,
-fastapi/security/__pycache__/oauth2.cpython-312.pyc,,
-fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc,,
-fastapi/security/__pycache__/utils.cpython-312.pyc,,
-fastapi/security/api_key.py,sha256=5AriUhrA_KgdtJRJ_BtCDgcTFOUlUUvDSultdIfdApc,9799
-fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141
-fastapi/security/http.py,sha256=gckOhSa1ubLpARU819pxKiZZnmnyg_co6AwQyNE8yxw,13518
-fastapi/security/oauth2.py,sha256=8sU0yRncO_1mK8rdUES1GRijPawi2ZGwLGWphWeS02w,22477
-fastapi/security/open_id_connect_url.py,sha256=pFvSVESThhjYXSDWPlFGtm9bN62JXHzuwnVfmtyNcZE,3158
-fastapi/security/utils.py,sha256=Gk6KGztJnYqvYFTmuQO7ow_icayiqP3HL762ZFRQjfU,286
-fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69
-fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76
-fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
-fastapi/types.py,sha256=W0HOmfeZw_3PcMDOa6GA-Or9okP9hf_260UkbCfKHY4,455
-fastapi/utils.py,sha256=sl-ddHjWbQiLUNLFiG93IXStAZgp8NSFwJsjo41mb04,5230
-fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/REQUESTED b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/WHEEL
deleted file mode 100644
index 2efd4ed..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: pdm-backend (2.4.6)
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/entry_points.txt b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/entry_points.txt
deleted file mode 100644
index b81849e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/entry_points.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-[console_scripts]
-fastapi = fastapi.cli:main
-
-[gui_scripts]
-
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/licenses/LICENSE
deleted file mode 100644
index 3e92463..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi-0.128.0.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2018 Sebastián Ramírez
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/__init__.py
deleted file mode 100644
index 6133787..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-
-__version__ = "0.128.0"
-
-from starlette import status as status
-
-from .applications import FastAPI as FastAPI
-from .background import BackgroundTasks as BackgroundTasks
-from .datastructures import UploadFile as UploadFile
-from .exceptions import HTTPException as HTTPException
-from .exceptions import WebSocketException as WebSocketException
-from .param_functions import Body as Body
-from .param_functions import Cookie as Cookie
-from .param_functions import Depends as Depends
-from .param_functions import File as File
-from .param_functions import Form as Form
-from .param_functions import Header as Header
-from .param_functions import Path as Path
-from .param_functions import Query as Query
-from .param_functions import Security as Security
-from .requests import Request as Request
-from .responses import Response as Response
-from .routing import APIRouter as APIRouter
-from .websockets import WebSocket as WebSocket
-from .websockets import WebSocketDisconnect as WebSocketDisconnect
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__main__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/__main__.py
deleted file mode 100644
index fc36465..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/__main__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from fastapi.cli import main
-
-main()
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 8321df2..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__main__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__main__.cpython-312.pyc
deleted file mode 100644
index 3ae7c07..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/__main__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc
deleted file mode 100644
index d2664be..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc
deleted file mode 100644
index e0081cf..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/cli.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/cli.cpython-312.pyc
deleted file mode 100644
index 4a99f9f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/cli.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc
deleted file mode 100644
index fa3f4da..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc
deleted file mode 100644
index 4b89595..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc
deleted file mode 100644
index 9d02f78..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc
deleted file mode 100644
index 0d7ab64..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc
deleted file mode 100644
index ebd0482..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc
deleted file mode 100644
index 175467f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc
deleted file mode 100644
index e1dd42f..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc
deleted file mode 100644
index 744780c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc
deleted file mode 100644
index a5f016d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc
deleted file mode 100644
index e4db996..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc
deleted file mode 100644
index af52ea5..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc
deleted file mode 100644
index bad5945..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/templating.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/templating.cpython-312.pyc
deleted file mode 100644
index c3dbeb0..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/templating.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc
deleted file mode 100644
index 36c23cf..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc
deleted file mode 100644
index 88878ab..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc
deleted file mode 100644
index 06a0c9d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc
deleted file mode 100644
index d3544ea..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__init__.py
deleted file mode 100644
index 3dfaf9b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from .shared import PYDANTIC_V2 as PYDANTIC_V2
-from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
-from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
-from .shared import field_annotation_is_scalar as field_annotation_is_scalar
-from .shared import is_pydantic_v1_model_class as is_pydantic_v1_model_class
-from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance
-from .shared import (
- is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
-)
-from .shared import (
- is_uploadfile_sequence_annotation as is_uploadfile_sequence_annotation,
-)
-from .shared import lenient_issubclass as lenient_issubclass
-from .shared import sequence_types as sequence_types
-from .shared import value_is_sequence as value_is_sequence
-from .v2 import BaseConfig as BaseConfig
-from .v2 import ModelField as ModelField
-from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError
-from .v2 import RequiredParam as RequiredParam
-from .v2 import Undefined as Undefined
-from .v2 import UndefinedType as UndefinedType
-from .v2 import Url as Url
-from .v2 import Validator as Validator
-from .v2 import _regenerate_error_with_loc as _regenerate_error_with_loc
-from .v2 import copy_field_info as copy_field_info
-from .v2 import create_body_model as create_body_model
-from .v2 import evaluate_forwardref as evaluate_forwardref
-from .v2 import get_cached_model_fields as get_cached_model_fields
-from .v2 import get_compat_model_name_map as get_compat_model_name_map
-from .v2 import get_definitions as get_definitions
-from .v2 import get_missing_field_error as get_missing_field_error
-from .v2 import get_schema_from_model_field as get_schema_from_model_field
-from .v2 import is_bytes_field as is_bytes_field
-from .v2 import is_bytes_sequence_field as is_bytes_sequence_field
-from .v2 import is_scalar_field as is_scalar_field
-from .v2 import is_scalar_sequence_field as is_scalar_sequence_field
-from .v2 import is_sequence_field as is_sequence_field
-from .v2 import serialize_sequence_value as serialize_sequence_value
-from .v2 import (
- with_info_plain_validator_function as with_info_plain_validator_function,
-)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index c7137c3..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc
deleted file mode 100644
index 6b9fac3..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc
deleted file mode 100644
index 638586c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/shared.py b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/shared.py
deleted file mode 100644
index 419b58f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/shared.py
+++ /dev/null
@@ -1,206 +0,0 @@
-import sys
-import types
-import typing
-import warnings
-from collections import deque
-from collections.abc import Mapping, Sequence
-from dataclasses import is_dataclass
-from typing import (
- Annotated,
- Any,
- Union,
-)
-
-from fastapi.types import UnionType
-from pydantic import BaseModel
-from pydantic.version import VERSION as PYDANTIC_VERSION
-from starlette.datastructures import UploadFile
-from typing_extensions import get_args, get_origin
-
-# Copy from Pydantic v2, compatible with v1
-if sys.version_info < (3, 10):
- WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined]
-else:
- WithArgsTypes: tuple[Any, ...] = (
- typing._GenericAlias, # type: ignore[attr-defined]
- types.GenericAlias,
- types.UnionType,
- ) # pyright: ignore[reportAttributeAccessIssue]
-
-PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
-PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
-
-
-sequence_annotation_to_type = {
- Sequence: list,
- list: list,
- tuple: tuple,
- set: set,
- frozenset: frozenset,
- deque: deque,
-}
-
-sequence_types = tuple(sequence_annotation_to_type.keys())
-
-Url: type[Any]
-
-
-# Copy of Pydantic v2, compatible with v1
-def lenient_issubclass(
- cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...], None]
-) -> bool:
- try:
- return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type]
- except TypeError: # pragma: no cover
- if isinstance(cls, WithArgsTypes):
- return False
- raise # pragma: no cover
-
-
-def _annotation_is_sequence(annotation: Union[type[Any], None]) -> bool:
- if lenient_issubclass(annotation, (str, bytes)):
- return False
- return lenient_issubclass(annotation, sequence_types)
-
-
-def field_annotation_is_sequence(annotation: Union[type[Any], None]) -> bool:
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- for arg in get_args(annotation):
- if field_annotation_is_sequence(arg):
- return True
- return False
- return _annotation_is_sequence(annotation) or _annotation_is_sequence(
- get_origin(annotation)
- )
-
-
-def value_is_sequence(value: Any) -> bool:
- return isinstance(value, sequence_types) and not isinstance(value, (str, bytes))
-
-
-def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool:
- return (
- lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
- or _annotation_is_sequence(annotation)
- or is_dataclass(annotation)
- )
-
-
-def field_annotation_is_complex(annotation: Union[type[Any], None]) -> bool:
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
-
- if origin is Annotated:
- return field_annotation_is_complex(get_args(annotation)[0])
-
- return (
- _annotation_is_complex(annotation)
- or _annotation_is_complex(origin)
- or hasattr(origin, "__pydantic_core_schema__")
- or hasattr(origin, "__get_pydantic_core_schema__")
- )
-
-
-def field_annotation_is_scalar(annotation: Any) -> bool:
- # handle Ellipsis here to make tuple[int, ...] work nicely
- return annotation is Ellipsis or not field_annotation_is_complex(annotation)
-
-
-def field_annotation_is_scalar_sequence(annotation: Union[type[Any], None]) -> bool:
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- at_least_one_scalar_sequence = False
- for arg in get_args(annotation):
- if field_annotation_is_scalar_sequence(arg):
- at_least_one_scalar_sequence = True
- continue
- elif not field_annotation_is_scalar(arg):
- return False
- return at_least_one_scalar_sequence
- return field_annotation_is_sequence(annotation) and all(
- field_annotation_is_scalar(sub_annotation)
- for sub_annotation in get_args(annotation)
- )
-
-
-def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool:
- if lenient_issubclass(annotation, bytes):
- return True
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- for arg in get_args(annotation):
- if lenient_issubclass(arg, bytes):
- return True
- return False
-
-
-def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:
- if lenient_issubclass(annotation, UploadFile):
- return True
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- for arg in get_args(annotation):
- if lenient_issubclass(arg, UploadFile):
- return True
- return False
-
-
-def is_bytes_sequence_annotation(annotation: Any) -> bool:
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- at_least_one = False
- for arg in get_args(annotation):
- if is_bytes_sequence_annotation(arg):
- at_least_one = True
- continue
- return at_least_one
- return field_annotation_is_sequence(annotation) and all(
- is_bytes_or_nonable_bytes_annotation(sub_annotation)
- for sub_annotation in get_args(annotation)
- )
-
-
-def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- at_least_one = False
- for arg in get_args(annotation):
- if is_uploadfile_sequence_annotation(arg):
- at_least_one = True
- continue
- return at_least_one
- return field_annotation_is_sequence(annotation) and all(
- is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
- for sub_annotation in get_args(annotation)
- )
-
-
-def is_pydantic_v1_model_instance(obj: Any) -> bool:
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", UserWarning)
- from pydantic import v1
- return isinstance(obj, v1.BaseModel)
-
-
-def is_pydantic_v1_model_class(cls: Any) -> bool:
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", UserWarning)
- from pydantic import v1
- return lenient_issubclass(cls, v1.BaseModel)
-
-
-def annotation_is_pydantic_v1(annotation: Any) -> bool:
- if is_pydantic_v1_model_class(annotation):
- return True
- origin = get_origin(annotation)
- if origin is Union or origin is UnionType:
- for arg in get_args(annotation):
- if is_pydantic_v1_model_class(arg):
- return True
- if field_annotation_is_sequence(annotation):
- for sub_annotation in get_args(annotation):
- if annotation_is_pydantic_v1(sub_annotation):
- return True
- return False
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/v2.py b/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/v2.py
deleted file mode 100644
index 25b6814..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/_compat/v2.py
+++ /dev/null
@@ -1,568 +0,0 @@
-import re
-import warnings
-from collections.abc import Sequence
-from copy import copy, deepcopy
-from dataclasses import dataclass, is_dataclass
-from enum import Enum
-from functools import lru_cache
-from typing import (
- Annotated,
- Any,
- Union,
- cast,
-)
-
-from fastapi._compat import shared
-from fastapi.openapi.constants import REF_TEMPLATE
-from fastapi.types import IncEx, ModelNameMap, UnionType
-from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model
-from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
-from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
-from pydantic import ValidationError as ValidationError
-from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined]
- GetJsonSchemaHandler as GetJsonSchemaHandler,
-)
-from pydantic._internal._typing_extra import eval_type_lenient
-from pydantic._internal._utils import lenient_issubclass as lenient_issubclass
-from pydantic.fields import FieldInfo as FieldInfo
-from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema
-from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
-from pydantic_core import CoreSchema as CoreSchema
-from pydantic_core import PydanticUndefined, PydanticUndefinedType
-from pydantic_core import Url as Url
-from typing_extensions import Literal, get_args, get_origin
-
-try:
- from pydantic_core.core_schema import (
- with_info_plain_validator_function as with_info_plain_validator_function,
- )
-except ImportError: # pragma: no cover
- from pydantic_core.core_schema import (
- general_plain_validator_function as with_info_plain_validator_function, # noqa: F401
- )
-
-RequiredParam = PydanticUndefined
-Undefined = PydanticUndefined
-UndefinedType = PydanticUndefinedType
-evaluate_forwardref = eval_type_lenient
-Validator = Any
-
-# TODO: remove when dropping support for Pydantic < v2.12.3
-_Attrs = {
- "default": ...,
- "default_factory": None,
- "alias": None,
- "alias_priority": None,
- "validation_alias": None,
- "serialization_alias": None,
- "title": None,
- "field_title_generator": None,
- "description": None,
- "examples": None,
- "exclude": None,
- "exclude_if": None,
- "discriminator": None,
- "deprecated": None,
- "json_schema_extra": None,
- "frozen": None,
- "validate_default": None,
- "repr": True,
- "init": None,
- "init_var": None,
- "kw_only": None,
-}
-
-
-# TODO: remove when dropping support for Pydantic < v2.12.3
-def asdict(field_info: FieldInfo) -> dict[str, Any]:
- attributes = {}
- for attr in _Attrs:
- value = getattr(field_info, attr, Undefined)
- if value is not Undefined:
- attributes[attr] = value
- return {
- "annotation": field_info.annotation,
- "metadata": field_info.metadata,
- "attributes": attributes,
- }
-
-
-class BaseConfig:
- pass
-
-
-class ErrorWrapper(Exception):
- pass
-
-
-@dataclass
-class ModelField:
- field_info: FieldInfo
- name: str
- mode: Literal["validation", "serialization"] = "validation"
- config: Union[ConfigDict, None] = None
-
- @property
- def alias(self) -> str:
- a = self.field_info.alias
- return a if a is not None else self.name
-
- @property
- def validation_alias(self) -> Union[str, None]:
- va = self.field_info.validation_alias
- if isinstance(va, str) and va:
- return va
- return None
-
- @property
- def serialization_alias(self) -> Union[str, None]:
- sa = self.field_info.serialization_alias
- return sa or None
-
- @property
- def required(self) -> bool:
- return self.field_info.is_required()
-
- @property
- def default(self) -> Any:
- return self.get_default()
-
- @property
- def type_(self) -> Any:
- return self.field_info.annotation
-
- def __post_init__(self) -> None:
- with warnings.catch_warnings():
- # Pydantic >= 2.12.0 warns about field specific metadata that is unused
- # (e.g. `TypeAdapter(Annotated[int, Field(alias='b')])`). In some cases, we
- # end up building the type adapter from a model field annotation so we
- # need to ignore the warning:
- if shared.PYDANTIC_VERSION_MINOR_TUPLE >= (2, 12):
- from pydantic.warnings import UnsupportedFieldAttributeWarning
-
- warnings.simplefilter(
- "ignore", category=UnsupportedFieldAttributeWarning
- )
- # TODO: remove after dropping support for Python 3.8 and
- # setting the min Pydantic to v2.12.3 that adds asdict()
- field_dict = asdict(self.field_info)
- annotated_args = (
- field_dict["annotation"],
- *field_dict["metadata"],
- # this FieldInfo needs to be created again so that it doesn't include
- # the old field info metadata and only the rest of the attributes
- Field(**field_dict["attributes"]),
- )
- self._type_adapter: TypeAdapter[Any] = TypeAdapter(
- Annotated[annotated_args],
- config=self.config,
- )
-
- def get_default(self) -> Any:
- if self.field_info.is_required():
- return Undefined
- return self.field_info.get_default(call_default_factory=True)
-
- def validate(
- self,
- value: Any,
- values: dict[str, Any] = {}, # noqa: B006
- *,
- loc: tuple[Union[int, str], ...] = (),
- ) -> tuple[Any, Union[list[dict[str, Any]], None]]:
- try:
- return (
- self._type_adapter.validate_python(value, from_attributes=True),
- None,
- )
- except ValidationError as exc:
- return None, _regenerate_error_with_loc(
- errors=exc.errors(include_url=False), loc_prefix=loc
- )
-
- def serialize(
- self,
- value: Any,
- *,
- mode: Literal["json", "python"] = "json",
- include: Union[IncEx, None] = None,
- exclude: Union[IncEx, None] = None,
- by_alias: bool = True,
- exclude_unset: bool = False,
- exclude_defaults: bool = False,
- exclude_none: bool = False,
- ) -> Any:
- # What calls this code passes a value that already called
- # self._type_adapter.validate_python(value)
- return self._type_adapter.dump_python(
- value,
- mode=mode,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
-
- def __hash__(self) -> int:
- # Each ModelField is unique for our purposes, to allow making a dict from
- # ModelField to its JSON Schema.
- return id(self)
-
-
-def _has_computed_fields(field: ModelField) -> bool:
- computed_fields = field._type_adapter.core_schema.get("schema", {}).get(
- "computed_fields", []
- )
- return len(computed_fields) > 0
-
-
-def get_schema_from_model_field(
- *,
- field: ModelField,
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
- ],
- separate_input_output_schemas: bool = True,
-) -> dict[str, Any]:
- override_mode: Union[Literal["validation"], None] = (
- None
- if (separate_input_output_schemas or _has_computed_fields(field))
- else "validation"
- )
- field_alias = (
- (field.validation_alias or field.alias)
- if field.mode == "validation"
- else (field.serialization_alias or field.alias)
- )
-
- # This expects that GenerateJsonSchema was already used to generate the definitions
- json_schema = field_mapping[(field, override_mode or field.mode)]
- if "$ref" not in json_schema:
- # TODO remove when deprecating Pydantic v1
- # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
- json_schema["title"] = field.field_info.title or field_alias.title().replace(
- "_", " "
- )
- return json_schema
-
-
-def get_definitions(
- *,
- fields: Sequence[ModelField],
- model_name_map: ModelNameMap,
- separate_input_output_schemas: bool = True,
-) -> tuple[
- dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
- dict[str, dict[str, Any]],
-]:
- schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
- validation_fields = [field for field in fields if field.mode == "validation"]
- serialization_fields = [field for field in fields if field.mode == "serialization"]
- flat_validation_models = get_flat_models_from_fields(
- validation_fields, known_models=set()
- )
- flat_serialization_models = get_flat_models_from_fields(
- serialization_fields, known_models=set()
- )
- flat_validation_model_fields = [
- ModelField(
- field_info=FieldInfo(annotation=model),
- name=model.__name__,
- mode="validation",
- )
- for model in flat_validation_models
- ]
- flat_serialization_model_fields = [
- ModelField(
- field_info=FieldInfo(annotation=model),
- name=model.__name__,
- mode="serialization",
- )
- for model in flat_serialization_models
- ]
- flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields
- input_types = {f.type_ for f in fields}
- unique_flat_model_fields = {
- f for f in flat_model_fields if f.type_ not in input_types
- }
- inputs = [
- (
- field,
- (
- field.mode
- if (separate_input_output_schemas or _has_computed_fields(field))
- else "validation"
- ),
- field._type_adapter.core_schema,
- )
- for field in list(fields) + list(unique_flat_model_fields)
- ]
- field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs)
- for item_def in cast(dict[str, dict[str, Any]], definitions).values():
- if "description" in item_def:
- item_description = cast(str, item_def["description"]).split("\f")[0]
- item_def["description"] = item_description
- new_mapping, new_definitions = _remap_definitions_and_field_mappings(
- model_name_map=model_name_map,
- definitions=definitions, # type: ignore[arg-type]
- field_mapping=field_mapping,
- )
- return new_mapping, new_definitions
-
-
-def _replace_refs(
- *,
- schema: dict[str, Any],
- old_name_to_new_name_map: dict[str, str],
-) -> dict[str, Any]:
- new_schema = deepcopy(schema)
- for key, value in new_schema.items():
- if key == "$ref":
- value = schema["$ref"]
- if isinstance(value, str):
- ref_name = schema["$ref"].split("/")[-1]
- if ref_name in old_name_to_new_name_map:
- new_name = old_name_to_new_name_map[ref_name]
- new_schema["$ref"] = REF_TEMPLATE.format(model=new_name)
- continue
- if isinstance(value, dict):
- new_schema[key] = _replace_refs(
- schema=value,
- old_name_to_new_name_map=old_name_to_new_name_map,
- )
- elif isinstance(value, list):
- new_value = []
- for item in value:
- if isinstance(item, dict):
- new_item = _replace_refs(
- schema=item,
- old_name_to_new_name_map=old_name_to_new_name_map,
- )
- new_value.append(new_item)
-
- else:
- new_value.append(item)
- new_schema[key] = new_value
- return new_schema
-
-
-def _remap_definitions_and_field_mappings(
- *,
- model_name_map: ModelNameMap,
- definitions: dict[str, Any],
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
- ],
-) -> tuple[
- dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
- dict[str, Any],
-]:
- old_name_to_new_name_map = {}
- for field_key, schema in field_mapping.items():
- model = field_key[0].type_
- if model not in model_name_map or "$ref" not in schema:
- continue
- new_name = model_name_map[model]
- old_name = schema["$ref"].split("/")[-1]
- if old_name in {f"{new_name}-Input", f"{new_name}-Output"}:
- continue
- old_name_to_new_name_map[old_name] = new_name
-
- new_field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
- ] = {}
- for field_key, schema in field_mapping.items():
- new_schema = _replace_refs(
- schema=schema,
- old_name_to_new_name_map=old_name_to_new_name_map,
- )
- new_field_mapping[field_key] = new_schema
-
- new_definitions = {}
- for key, value in definitions.items():
- if key in old_name_to_new_name_map:
- new_key = old_name_to_new_name_map[key]
- else:
- new_key = key
- new_value = _replace_refs(
- schema=value,
- old_name_to_new_name_map=old_name_to_new_name_map,
- )
- new_definitions[new_key] = new_value
- return new_field_mapping, new_definitions
-
-
-def is_scalar_field(field: ModelField) -> bool:
- from fastapi import params
-
- return shared.field_annotation_is_scalar(
- field.field_info.annotation
- ) and not isinstance(field.field_info, params.Body)
-
-
-def is_sequence_field(field: ModelField) -> bool:
- return shared.field_annotation_is_sequence(field.field_info.annotation)
-
-
-def is_scalar_sequence_field(field: ModelField) -> bool:
- return shared.field_annotation_is_scalar_sequence(field.field_info.annotation)
-
-
-def is_bytes_field(field: ModelField) -> bool:
- return shared.is_bytes_or_nonable_bytes_annotation(field.type_)
-
-
-def is_bytes_sequence_field(field: ModelField) -> bool:
- return shared.is_bytes_sequence_annotation(field.type_)
-
-
-def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
- cls = type(field_info)
- merged_field_info = cls.from_annotation(annotation)
- new_field_info = copy(field_info)
- new_field_info.metadata = merged_field_info.metadata
- new_field_info.annotation = merged_field_info.annotation
- return new_field_info
-
-
-def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
- origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation
- if origin_type is Union or origin_type is UnionType: # Handle optional sequences
- union_args = get_args(field.field_info.annotation)
- for union_arg in union_args:
- if union_arg is type(None):
- continue
- origin_type = get_origin(union_arg) or union_arg
- break
- assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type]
- return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index]
-
-
-def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]:
- error = ValidationError.from_exception_data(
- "Field required", [{"type": "missing", "loc": loc, "input": {}}]
- ).errors(include_url=False)[0]
- error["input"] = None
- return error # type: ignore[return-value]
-
-
-def create_body_model(
- *, fields: Sequence[ModelField], model_name: str
-) -> type[BaseModel]:
- field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields}
- BodyModel: type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload]
- return BodyModel
-
-
-def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
- model_fields: list[ModelField] = []
- for name, field_info in model.model_fields.items():
- type_ = field_info.annotation
- if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_):
- model_config = None
- else:
- model_config = model.model_config
- model_fields.append(
- ModelField(
- field_info=field_info,
- name=name,
- config=model_config,
- )
- )
- return model_fields
-
-
-@lru_cache
-def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:
- return get_model_fields(model) # type: ignore[return-value]
-
-
-# Duplicate of several schema functions from Pydantic v1 to make them compatible with
-# Pydantic v2 and allow mixing the models
-
-TypeModelOrEnum = Union[type["BaseModel"], type[Enum]]
-TypeModelSet = set[TypeModelOrEnum]
-
-
-def normalize_name(name: str) -> str:
- return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name)
-
-
-def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]:
- name_model_map = {}
- for model in unique_models:
- model_name = normalize_name(model.__name__)
- name_model_map[model_name] = model
- return {v: k for k, v in name_model_map.items()}
-
-
-def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap:
- all_flat_models = set()
-
- v2_model_fields = [field for field in fields if isinstance(field, ModelField)]
- v2_flat_models = get_flat_models_from_fields(v2_model_fields, known_models=set())
- all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type]
-
- model_name_map = get_model_name_map(all_flat_models) # type: ignore[arg-type]
- return model_name_map
-
-
-def get_flat_models_from_model(
- model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None
-) -> TypeModelSet:
- known_models = known_models or set()
- fields = get_model_fields(model)
- get_flat_models_from_fields(fields, known_models=known_models)
- return known_models
-
-
-def get_flat_models_from_annotation(
- annotation: Any, known_models: TypeModelSet
-) -> TypeModelSet:
- origin = get_origin(annotation)
- if origin is not None:
- for arg in get_args(annotation):
- if lenient_issubclass(arg, (BaseModel, Enum)) and arg not in known_models:
- known_models.add(arg)
- if lenient_issubclass(arg, BaseModel):
- get_flat_models_from_model(arg, known_models=known_models)
- else:
- get_flat_models_from_annotation(arg, known_models=known_models)
- return known_models
-
-
-def get_flat_models_from_field(
- field: ModelField, known_models: TypeModelSet
-) -> TypeModelSet:
- field_type = field.type_
- if lenient_issubclass(field_type, BaseModel):
- if field_type in known_models:
- return known_models
- known_models.add(field_type)
- get_flat_models_from_model(field_type, known_models=known_models)
- elif lenient_issubclass(field_type, Enum):
- known_models.add(field_type)
- else:
- get_flat_models_from_annotation(field_type, known_models=known_models)
- return known_models
-
-
-def get_flat_models_from_fields(
- fields: Sequence[ModelField], known_models: TypeModelSet
-) -> TypeModelSet:
- for field in fields:
- get_flat_models_from_field(field, known_models=known_models)
- return known_models
-
-
-def _regenerate_error_with_loc(
- *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...]
-) -> list[dict[str, Any]]:
- updated_loc_errors: list[Any] = [
- {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors
- ]
-
- return updated_loc_errors
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/applications.py b/backend/.venv/lib/python3.12/site-packages/fastapi/applications.py
deleted file mode 100644
index 54175cb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/applications.py
+++ /dev/null
@@ -1,4669 +0,0 @@
-from collections.abc import Awaitable, Coroutine, Sequence
-from enum import Enum
-from typing import (
- Annotated,
- Any,
- Callable,
- Optional,
- TypeVar,
- Union,
-)
-
-from annotated_doc import Doc
-from fastapi import routing
-from fastapi.datastructures import Default, DefaultPlaceholder
-from fastapi.exception_handlers import (
- http_exception_handler,
- request_validation_exception_handler,
- websocket_request_validation_exception_handler,
-)
-from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
-from fastapi.logger import logger
-from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
-from fastapi.openapi.docs import (
- get_redoc_html,
- get_swagger_ui_html,
- get_swagger_ui_oauth2_redirect_html,
-)
-from fastapi.openapi.utils import get_openapi
-from fastapi.params import Depends
-from fastapi.types import DecoratedCallable, IncEx
-from fastapi.utils import generate_unique_id
-from starlette.applications import Starlette
-from starlette.datastructures import State
-from starlette.exceptions import HTTPException
-from starlette.middleware import Middleware
-from starlette.middleware.base import BaseHTTPMiddleware
-from starlette.middleware.errors import ServerErrorMiddleware
-from starlette.middleware.exceptions import ExceptionMiddleware
-from starlette.requests import Request
-from starlette.responses import HTMLResponse, JSONResponse, Response
-from starlette.routing import BaseRoute
-from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send
-from typing_extensions import deprecated
-
-AppType = TypeVar("AppType", bound="FastAPI")
-
-
-class FastAPI(Starlette):
- """
- `FastAPI` app class, the main entrypoint to use FastAPI.
-
- Read more in the
- [FastAPI docs for First Steps](https://fastapi.tiangolo.com/tutorial/first-steps/).
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
- ```
- """
-
- def __init__(
- self: AppType,
- *,
- debug: Annotated[
- bool,
- Doc(
- """
- Boolean indicating if debug tracebacks should be returned on server
- errors.
-
- Read more in the
- [Starlette docs for Applications](https://www.starlette.dev/applications/#instantiating-the-application).
- """
- ),
- ] = False,
- routes: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- **Note**: you probably shouldn't use this parameter, it is inherited
- from Starlette and supported for compatibility.
-
- ---
-
- A list of routes to serve incoming HTTP and WebSocket requests.
- """
- ),
- deprecated(
- """
- You normally wouldn't use this parameter with FastAPI, it is inherited
- from Starlette and supported for compatibility.
-
- In FastAPI, you normally would use the *path operation methods*,
- like `app.get()`, `app.post()`, etc.
- """
- ),
- ] = None,
- title: Annotated[
- str,
- Doc(
- """
- The title of the API.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(title="ChimichangApp")
- ```
- """
- ),
- ] = "FastAPI",
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A short summary of the API.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(summary="Deadpond's favorite app. Nuff said.")
- ```
- """
- ),
- ] = None,
- description: Annotated[
- str,
- Doc(
- '''
- A description of the API. Supports Markdown (using
- [CommonMark syntax](https://commonmark.org/)).
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(
- description="""
- ChimichangApp API helps you do awesome stuff. 🚀
-
- ## Items
-
- You can **read items**.
-
- ## Users
-
- You will be able to:
-
- * **Create users** (_not implemented_).
- * **Read users** (_not implemented_).
-
- """
- )
- ```
- '''
- ),
- ] = "",
- version: Annotated[
- str,
- Doc(
- """
- The version of the API.
-
- **Note** This is the version of your application, not the version of
- the OpenAPI specification nor the version of FastAPI being used.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(version="0.0.1")
- ```
- """
- ),
- ] = "0.1.0",
- openapi_url: Annotated[
- Optional[str],
- Doc(
- """
- The URL where the OpenAPI schema will be served from.
-
- If you set it to `None`, no OpenAPI schema will be served publicly, and
- the default automatic endpoints `/docs` and `/redoc` will also be
- disabled.
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#openapi-url).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(openapi_url="/api/v1/openapi.json")
- ```
- """
- ),
- ] = "/openapi.json",
- openapi_tags: Annotated[
- Optional[list[dict[str, Any]]],
- Doc(
- """
- A list of tags used by OpenAPI, these are the same `tags` you can set
- in the *path operations*, like:
-
- * `@app.get("/users/", tags=["users"])`
- * `@app.get("/items/", tags=["items"])`
-
- The order of the tags can be used to specify the order shown in
- tools like Swagger UI, used in the automatic path `/docs`.
-
- It's not required to specify all the tags used.
-
- The tags that are not declared MAY be organized randomly or based
- on the tools' logic. Each tag name in the list MUST be unique.
-
- The value of each item is a `dict` containing:
-
- * `name`: The name of the tag.
- * `description`: A short description of the tag.
- [CommonMark syntax](https://commonmark.org/) MAY be used for rich
- text representation.
- * `externalDocs`: Additional external documentation for this tag. If
- provided, it would contain a `dict` with:
- * `description`: A short description of the target documentation.
- [CommonMark syntax](https://commonmark.org/) MAY be used for
- rich text representation.
- * `url`: The URL for the target documentation. Value MUST be in
- the form of a URL.
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-tags).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- tags_metadata = [
- {
- "name": "users",
- "description": "Operations with users. The **login** logic is also here.",
- },
- {
- "name": "items",
- "description": "Manage items. So _fancy_ they have their own docs.",
- "externalDocs": {
- "description": "Items external docs",
- "url": "https://fastapi.tiangolo.com/",
- },
- },
- ]
-
- app = FastAPI(openapi_tags=tags_metadata)
- ```
- """
- ),
- ] = None,
- servers: Annotated[
- Optional[list[dict[str, Union[str, Any]]]],
- Doc(
- """
- A `list` of `dict`s with connectivity information to a target server.
-
- You would use it, for example, if your application is served from
- different domains and you want to use the same Swagger UI in the
- browser to interact with each of them (instead of having multiple
- browser tabs open). Or if you want to leave fixed the possible URLs.
-
- If the servers `list` is not provided, or is an empty `list`, the
- `servers` property in the generated OpenAPI will be:
-
- * a `dict` with a `url` value of the application's mounting point
- (`root_path`) if it's different from `/`.
- * otherwise, the `servers` property will be omitted from the OpenAPI
- schema.
-
- Each item in the `list` is a `dict` containing:
-
- * `url`: A URL to the target host. This URL supports Server Variables
- and MAY be relative, to indicate that the host location is relative
- to the location where the OpenAPI document is being served. Variable
- substitutions will be made when a variable is named in `{`brackets`}`.
- * `description`: An optional string describing the host designated by
- the URL. [CommonMark syntax](https://commonmark.org/) MAY be used for
- rich text representation.
- * `variables`: A `dict` between a variable name and its value. The value
- is used for substitution in the server's URL template.
-
- Read more in the
- [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#additional-servers).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(
- servers=[
- {"url": "https://stag.example.com", "description": "Staging environment"},
- {"url": "https://prod.example.com", "description": "Production environment"},
- ]
- )
- ```
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of global dependencies, they will be applied to each
- *path operation*, including in sub-routers.
-
- Read more about it in the
- [FastAPI docs for Global Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/).
-
- **Example**
-
- ```python
- from fastapi import Depends, FastAPI
-
- from .dependencies import func_dep_1, func_dep_2
-
- app = FastAPI(dependencies=[Depends(func_dep_1), Depends(func_dep_2)])
- ```
- """
- ),
- ] = None,
- default_response_class: Annotated[
- type[Response],
- Doc(
- """
- The default response class to be used.
-
- Read more in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
- from fastapi.responses import ORJSONResponse
-
- app = FastAPI(default_response_class=ORJSONResponse)
- ```
- """
- ),
- ] = Default(JSONResponse),
- redirect_slashes: Annotated[
- bool,
- Doc(
- """
- Whether to detect and redirect slashes in URLs when the client doesn't
- use the same format.
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(redirect_slashes=True) # the default
-
- @app.get("/items/")
- async def read_items():
- return [{"item_id": "Foo"}]
- ```
-
- With this app, if a client goes to `/items` (without a trailing slash),
- they will be automatically redirected with an HTTP status code of 307
- to `/items/`.
- """
- ),
- ] = True,
- docs_url: Annotated[
- Optional[str],
- Doc(
- """
- The path to the automatic interactive API documentation.
- It is handled in the browser by Swagger UI.
-
- The default URL is `/docs`. You can disable it by setting it to `None`.
-
- If `openapi_url` is set to `None`, this will be automatically disabled.
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(docs_url="/documentation", redoc_url=None)
- ```
- """
- ),
- ] = "/docs",
- redoc_url: Annotated[
- Optional[str],
- Doc(
- """
- The path to the alternative automatic interactive API documentation
- provided by ReDoc.
-
- The default URL is `/redoc`. You can disable it by setting it to `None`.
-
- If `openapi_url` is set to `None`, this will be automatically disabled.
-
- Read more in the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(docs_url="/documentation", redoc_url="redocumentation")
- ```
- """
- ),
- ] = "/redoc",
- swagger_ui_oauth2_redirect_url: Annotated[
- Optional[str],
- Doc(
- """
- The OAuth2 redirect endpoint for the Swagger UI.
-
- By default it is `/docs/oauth2-redirect`.
-
- This is only used if you use OAuth2 (with the "Authorize" button)
- with Swagger UI.
- """
- ),
- ] = "/docs/oauth2-redirect",
- swagger_ui_init_oauth: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- OAuth2 configuration for the Swagger UI, by default shown at `/docs`.
-
- Read more about the available configuration options in the
- [Swagger UI docs](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/).
- """
- ),
- ] = None,
- middleware: Annotated[
- Optional[Sequence[Middleware]],
- Doc(
- """
- List of middleware to be added when creating the application.
-
- In FastAPI you would normally do this with `app.add_middleware()`
- instead.
-
- Read more in the
- [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/).
- """
- ),
- ] = None,
- exception_handlers: Annotated[
- Optional[
- dict[
- Union[int, type[Exception]],
- Callable[[Request, Any], Coroutine[Any, Any, Response]],
- ]
- ],
- Doc(
- """
- A dictionary with handlers for exceptions.
-
- In FastAPI, you would normally use the decorator
- `@app.exception_handler()`.
-
- Read more in the
- [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/).
- """
- ),
- ] = None,
- on_startup: Annotated[
- Optional[Sequence[Callable[[], Any]]],
- Doc(
- """
- A list of startup event handler functions.
-
- You should instead use the `lifespan` handlers.
-
- Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- on_shutdown: Annotated[
- Optional[Sequence[Callable[[], Any]]],
- Doc(
- """
- A list of shutdown event handler functions.
-
- You should instead use the `lifespan` handlers.
-
- Read more in the
- [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- lifespan: Annotated[
- Optional[Lifespan[AppType]],
- Doc(
- """
- A `Lifespan` context manager handler. This replaces `startup` and
- `shutdown` functions with a single context manager.
-
- Read more in the
- [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- terms_of_service: Annotated[
- Optional[str],
- Doc(
- """
- A URL to the Terms of Service for your API.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more at the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- app = FastAPI(terms_of_service="http://example.com/terms/")
- ```
- """
- ),
- ] = None,
- contact: Annotated[
- Optional[dict[str, Union[str, Any]]],
- Doc(
- """
- A dictionary with the contact information for the exposed API.
-
- It can contain several fields.
-
- * `name`: (`str`) The name of the contact person/organization.
- * `url`: (`str`) A URL pointing to the contact information. MUST be in
- the format of a URL.
- * `email`: (`str`) The email address of the contact person/organization.
- MUST be in the format of an email address.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more at the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- app = FastAPI(
- contact={
- "name": "Deadpoolio the Amazing",
- "url": "http://x-force.example.com/contact/",
- "email": "dp@x-force.example.com",
- }
- )
- ```
- """
- ),
- ] = None,
- license_info: Annotated[
- Optional[dict[str, Union[str, Any]]],
- Doc(
- """
- A dictionary with the license information for the exposed API.
-
- It can contain several fields.
-
- * `name`: (`str`) **REQUIRED** (if a `license_info` is set). The
- license name used for the API.
- * `identifier`: (`str`) An [SPDX](https://spdx.dev/) license expression
- for the API. The `identifier` field is mutually exclusive of the `url`
- field. Available since OpenAPI 3.1.0, FastAPI 0.99.0.
- * `url`: (`str`) A URL to the license used for the API. This MUST be
- the format of a URL.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more at the
- [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api).
-
- **Example**
-
- ```python
- app = FastAPI(
- license_info={
- "name": "Apache 2.0",
- "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
- }
- )
- ```
- """
- ),
- ] = None,
- openapi_prefix: Annotated[
- str,
- Doc(
- """
- A URL prefix for the OpenAPI URL.
- """
- ),
- deprecated(
- """
- "openapi_prefix" has been deprecated in favor of "root_path", which
- follows more closely the ASGI standard, is simpler, and more
- automatic.
- """
- ),
- ] = "",
- root_path: Annotated[
- str,
- Doc(
- """
- A path prefix handled by a proxy that is not seen by the application
- but is seen by external clients, which affects things like Swagger UI.
-
- Read more about it at the
- [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(root_path="/api/v1")
- ```
- """
- ),
- ] = "",
- root_path_in_servers: Annotated[
- bool,
- Doc(
- """
- To disable automatically generating the URLs in the `servers` field
- in the autogenerated OpenAPI using the `root_path`.
-
- Read more about it in the
- [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#disable-automatic-server-from-root_path).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI(root_path_in_servers=False)
- ```
- """
- ),
- ] = True,
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses to be shown in OpenAPI.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
-
- And in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- OpenAPI callbacks that should apply to all *path operations*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- webhooks: Annotated[
- Optional[routing.APIRouter],
- Doc(
- """
- Add OpenAPI webhooks. This is similar to `callbacks` but it doesn't
- depend on specific *path operations*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- **Note**: This is available since OpenAPI 3.1.0, FastAPI 0.99.0.
-
- Read more about it in the
- [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark all *path operations* as deprecated. You probably don't need it,
- but it's available.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) all the *path operations* in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- swagger_ui_parameters: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Parameters to configure Swagger UI, the autogenerated interactive API
- documentation (by default at `/docs`).
-
- Read more about it in the
- [FastAPI docs about how to Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- separate_input_output_schemas: Annotated[
- bool,
- Doc(
- """
- Whether to generate separate OpenAPI schemas for request body and
- response body when the results would be more precise.
-
- This is particularly useful when automatically generating clients.
-
- For example, if you have a model like:
-
- ```python
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- tags: list[str] = []
- ```
-
- When `Item` is used for input, a request body, `tags` is not required,
- the client doesn't have to provide it.
-
- But when using `Item` for output, for a response body, `tags` is always
- available because it has a default value, even if it's just an empty
- list. So, the client should be able to always expect it.
-
- In this case, there would be two different schemas, one for input and
- another one for output.
- """
- ),
- ] = True,
- openapi_external_docs: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- This field allows you to provide additional external documentation links.
- If provided, it must be a dictionary containing:
-
- * `description`: A brief description of the external documentation.
- * `url`: The URL pointing to the external documentation. The value **MUST**
- be a valid URL format.
-
- **Example**:
-
- ```python
- from fastapi import FastAPI
-
- external_docs = {
- "description": "Detailed API Reference",
- "url": "https://example.com/api-docs",
- }
-
- app = FastAPI(openapi_external_docs=external_docs)
- ```
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Extra keyword arguments to be stored in the app, not used by FastAPI
- anywhere.
- """
- ),
- ],
- ) -> None:
- self.debug = debug
- self.title = title
- self.summary = summary
- self.description = description
- self.version = version
- self.terms_of_service = terms_of_service
- self.contact = contact
- self.license_info = license_info
- self.openapi_url = openapi_url
- self.openapi_tags = openapi_tags
- self.root_path_in_servers = root_path_in_servers
- self.docs_url = docs_url
- self.redoc_url = redoc_url
- self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
- self.swagger_ui_init_oauth = swagger_ui_init_oauth
- self.swagger_ui_parameters = swagger_ui_parameters
- self.servers = servers or []
- self.separate_input_output_schemas = separate_input_output_schemas
- self.openapi_external_docs = openapi_external_docs
- self.extra = extra
- self.openapi_version: Annotated[
- str,
- Doc(
- """
- The version string of OpenAPI.
-
- FastAPI will generate OpenAPI version 3.1.0, and will output that as
- the OpenAPI version. But some tools, even though they might be
- compatible with OpenAPI 3.1.0, might not recognize it as a valid.
-
- So you could override this value to trick those tools into using
- the generated OpenAPI. Have in mind that this is a hack. But if you
- avoid using features added in OpenAPI 3.1.0, it might work for your
- use case.
-
- This is not passed as a parameter to the `FastAPI` class to avoid
- giving the false idea that FastAPI would generate a different OpenAPI
- schema. It is only available as an attribute.
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
-
- app.openapi_version = "3.0.2"
- ```
- """
- ),
- ] = "3.1.0"
- self.openapi_schema: Optional[dict[str, Any]] = None
- if self.openapi_url:
- assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
- assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
- # TODO: remove when discarding the openapi_prefix parameter
- if openapi_prefix:
- logger.warning(
- '"openapi_prefix" has been deprecated in favor of "root_path", which '
- "follows more closely the ASGI standard, is simpler, and more "
- "automatic. Check the docs at "
- "https://fastapi.tiangolo.com/advanced/sub-applications/"
- )
- self.webhooks: Annotated[
- routing.APIRouter,
- Doc(
- """
- The `app.webhooks` attribute is an `APIRouter` with the *path
- operations* that will be used just for documentation of webhooks.
-
- Read more about it in the
- [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/).
- """
- ),
- ] = webhooks or routing.APIRouter()
- self.root_path = root_path or openapi_prefix
- self.state: Annotated[
- State,
- Doc(
- """
- A state object for the application. This is the same object for the
- entire application, it doesn't change from request to request.
-
- You normally wouldn't use this in FastAPI, for most of the cases you
- would instead use FastAPI dependencies.
-
- This is simply inherited from Starlette.
-
- Read more about it in the
- [Starlette docs for Applications](https://www.starlette.dev/applications/#storing-state-on-the-app-instance).
- """
- ),
- ] = State()
- self.dependency_overrides: Annotated[
- dict[Callable[..., Any], Callable[..., Any]],
- Doc(
- """
- A dictionary with overrides for the dependencies.
-
- Each key is the original dependency callable, and the value is the
- actual dependency that should be called.
-
- This is for testing, to replace expensive dependencies with testing
- versions.
-
- Read more about it in the
- [FastAPI docs for Testing Dependencies with Overrides](https://fastapi.tiangolo.com/advanced/testing-dependencies/).
- """
- ),
- ] = {}
- self.router: routing.APIRouter = routing.APIRouter(
- routes=routes,
- redirect_slashes=redirect_slashes,
- dependency_overrides_provider=self,
- on_startup=on_startup,
- on_shutdown=on_shutdown,
- lifespan=lifespan,
- default_response_class=default_response_class,
- dependencies=dependencies,
- callbacks=callbacks,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- responses=responses,
- generate_unique_id_function=generate_unique_id_function,
- )
- self.exception_handlers: dict[
- Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]]
- ] = {} if exception_handlers is None else dict(exception_handlers)
- self.exception_handlers.setdefault(HTTPException, http_exception_handler)
- self.exception_handlers.setdefault(
- RequestValidationError, request_validation_exception_handler
- )
- self.exception_handlers.setdefault(
- WebSocketRequestValidationError,
- # Starlette still has incorrect type specification for the handlers
- websocket_request_validation_exception_handler, # type: ignore
- )
-
- self.user_middleware: list[Middleware] = (
- [] if middleware is None else list(middleware)
- )
- self.middleware_stack: Union[ASGIApp, None] = None
- self.setup()
-
- def build_middleware_stack(self) -> ASGIApp:
- # Duplicate/override from Starlette to add AsyncExitStackMiddleware
- # inside of ExceptionMiddleware, inside of custom user middlewares
- debug = self.debug
- error_handler = None
- exception_handlers: dict[Any, ExceptionHandler] = {}
-
- for key, value in self.exception_handlers.items():
- if key in (500, Exception):
- error_handler = value
- else:
- exception_handlers[key] = value
-
- middleware = (
- [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
- + self.user_middleware
- + [
- Middleware(
- ExceptionMiddleware, handlers=exception_handlers, debug=debug
- ),
- # Add FastAPI-specific AsyncExitStackMiddleware for closing files.
- # Before this was also used for closing dependencies with yield but
- # those now have their own AsyncExitStack, to properly support
- # streaming responses while keeping compatibility with the previous
- # versions (as of writing 0.117.1) that allowed doing
- # except HTTPException inside a dependency with yield.
- # This needs to happen after user middlewares because those create a
- # new contextvars context copy by using a new AnyIO task group.
- # This AsyncExitStack preserves the context for contextvars, not
- # strictly necessary for closing files but it was one of the original
- # intentions.
- # If the AsyncExitStack lived outside of the custom middlewares and
- # contextvars were set, for example in a dependency with 'yield'
- # in that internal contextvars context, the values would not be
- # available in the outer context of the AsyncExitStack.
- # By placing the middleware and the AsyncExitStack here, inside all
- # user middlewares, the same context is used.
- # This is currently not needed, only for closing files, but used to be
- # important when dependencies with yield were closed here.
- Middleware(AsyncExitStackMiddleware),
- ]
- )
-
- app = self.router
- for cls, args, kwargs in reversed(middleware):
- app = cls(app, *args, **kwargs)
- return app
-
- def openapi(self) -> dict[str, Any]:
- """
- Generate the OpenAPI schema of the application. This is called by FastAPI
- internally.
-
- The first time it is called it stores the result in the attribute
- `app.openapi_schema`, and next times it is called, it just returns that same
- result. To avoid the cost of generating the schema every time.
-
- If you need to modify the generated OpenAPI schema, you could modify it.
-
- Read more in the
- [FastAPI docs for OpenAPI](https://fastapi.tiangolo.com/how-to/extending-openapi/).
- """
- if not self.openapi_schema:
- self.openapi_schema = get_openapi(
- title=self.title,
- version=self.version,
- openapi_version=self.openapi_version,
- summary=self.summary,
- description=self.description,
- terms_of_service=self.terms_of_service,
- contact=self.contact,
- license_info=self.license_info,
- routes=self.routes,
- webhooks=self.webhooks.routes,
- tags=self.openapi_tags,
- servers=self.servers,
- separate_input_output_schemas=self.separate_input_output_schemas,
- external_docs=self.openapi_external_docs,
- )
- return self.openapi_schema
-
- def setup(self) -> None:
- if self.openapi_url:
- urls = (server_data.get("url") for server_data in self.servers)
- server_urls = {url for url in urls if url}
-
- async def openapi(req: Request) -> JSONResponse:
- root_path = req.scope.get("root_path", "").rstrip("/")
- if root_path not in server_urls:
- if root_path and self.root_path_in_servers:
- self.servers.insert(0, {"url": root_path})
- server_urls.add(root_path)
- return JSONResponse(self.openapi())
-
- self.add_route(self.openapi_url, openapi, include_in_schema=False)
- if self.openapi_url and self.docs_url:
-
- async def swagger_ui_html(req: Request) -> HTMLResponse:
- root_path = req.scope.get("root_path", "").rstrip("/")
- openapi_url = root_path + self.openapi_url
- oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
- if oauth2_redirect_url:
- oauth2_redirect_url = root_path + oauth2_redirect_url
- return get_swagger_ui_html(
- openapi_url=openapi_url,
- title=f"{self.title} - Swagger UI",
- oauth2_redirect_url=oauth2_redirect_url,
- init_oauth=self.swagger_ui_init_oauth,
- swagger_ui_parameters=self.swagger_ui_parameters,
- )
-
- self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
-
- if self.swagger_ui_oauth2_redirect_url:
-
- async def swagger_ui_redirect(req: Request) -> HTMLResponse:
- return get_swagger_ui_oauth2_redirect_html()
-
- self.add_route(
- self.swagger_ui_oauth2_redirect_url,
- swagger_ui_redirect,
- include_in_schema=False,
- )
- if self.openapi_url and self.redoc_url:
-
- async def redoc_html(req: Request) -> HTMLResponse:
- root_path = req.scope.get("root_path", "").rstrip("/")
- openapi_url = root_path + self.openapi_url
- return get_redoc_html(
- openapi_url=openapi_url, title=f"{self.title} - ReDoc"
- )
-
- self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
-
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
- if self.root_path:
- scope["root_path"] = self.root_path
- await super().__call__(scope, receive, send)
-
- def add_api_route(
- self,
- path: str,
- endpoint: Callable[..., Any],
- *,
- response_model: Any = Default(None),
- status_code: Optional[int] = None,
- tags: Optional[list[Union[str, Enum]]] = None,
- dependencies: Optional[Sequence[Depends]] = None,
- summary: Optional[str] = None,
- description: Optional[str] = None,
- response_description: str = "Successful Response",
- responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
- deprecated: Optional[bool] = None,
- methods: Optional[list[str]] = None,
- operation_id: Optional[str] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- include_in_schema: bool = True,
- response_class: Union[type[Response], DefaultPlaceholder] = Default(
- JSONResponse
- ),
- name: Optional[str] = None,
- openapi_extra: Optional[dict[str, Any]] = None,
- generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
- generate_unique_id
- ),
- ) -> None:
- self.router.add_api_route(
- path,
- endpoint=endpoint,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=methods,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def api_route(
- self,
- path: str,
- *,
- response_model: Any = Default(None),
- status_code: Optional[int] = None,
- tags: Optional[list[Union[str, Enum]]] = None,
- dependencies: Optional[Sequence[Depends]] = None,
- summary: Optional[str] = None,
- description: Optional[str] = None,
- response_description: str = "Successful Response",
- responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
- deprecated: Optional[bool] = None,
- methods: Optional[list[str]] = None,
- operation_id: Optional[str] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- include_in_schema: bool = True,
- response_class: type[Response] = Default(JSONResponse),
- name: Optional[str] = None,
- openapi_extra: Optional[dict[str, Any]] = None,
- generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
- generate_unique_id
- ),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.router.add_api_route(
- path,
- func,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=methods,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
- return func
-
- return decorator
-
- def add_api_websocket_route(
- self,
- path: str,
- endpoint: Callable[..., Any],
- name: Optional[str] = None,
- *,
- dependencies: Optional[Sequence[Depends]] = None,
- ) -> None:
- self.router.add_api_websocket_route(
- path,
- endpoint,
- name=name,
- dependencies=dependencies,
- )
-
- def websocket(
- self,
- path: Annotated[
- str,
- Doc(
- """
- WebSocket path.
- """
- ),
- ],
- name: Annotated[
- Optional[str],
- Doc(
- """
- A name for the WebSocket. Only used internally.
- """
- ),
- ] = None,
- *,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be used for this
- WebSocket.
-
- Read more about it in the
- [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
- """
- ),
- ] = None,
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Decorate a WebSocket function.
-
- Read more about it in the
- [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
-
- **Example**
-
- ```python
- from fastapi import FastAPI, WebSocket
-
- app = FastAPI()
-
- @app.websocket("/ws")
- async def websocket_endpoint(websocket: WebSocket):
- await websocket.accept()
- while True:
- data = await websocket.receive_text()
- await websocket.send_text(f"Message text was: {data}")
- ```
- """
-
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_api_websocket_route(
- path,
- func,
- name=name,
- dependencies=dependencies,
- )
- return func
-
- return decorator
-
- def include_router(
- self,
- router: Annotated[routing.APIRouter, Doc("The `APIRouter` to include.")],
- *,
- prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "",
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to all the *path operations* in this
- router.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to all the
- *path operations* in this router.
-
- Read more about it in the
- [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
-
- **Example**
-
- ```python
- from fastapi import Depends, FastAPI
-
- from .dependencies import get_token_header
- from .internal import admin
-
- app = FastAPI()
-
- app.include_router(
- admin.router,
- dependencies=[Depends(get_token_header)],
- )
- ```
- """
- ),
- ] = None,
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses to be shown in OpenAPI.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
-
- And in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark all the *path operations* in this router as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- from .internal import old_api
-
- app = FastAPI()
-
- app.include_router(
- old_api.router,
- deprecated=True,
- )
- ```
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include (or not) all the *path operations* in this router in the
- generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
-
- from .internal import old_api
-
- app = FastAPI()
-
- app.include_router(
- old_api.router,
- include_in_schema=False,
- )
- ```
- """
- ),
- ] = True,
- default_response_class: Annotated[
- type[Response],
- Doc(
- """
- Default response class to be used for the *path operations* in this
- router.
-
- Read more in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
-
- **Example**
-
- ```python
- from fastapi import FastAPI
- from fastapi.responses import ORJSONResponse
-
- from .internal import old_api
-
- app = FastAPI()
-
- app.include_router(
- old_api.router,
- default_response_class=ORJSONResponse,
- )
- ```
- """
- ),
- ] = Default(JSONResponse),
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> None:
- """
- Include an `APIRouter` in the same app.
-
- Read more about it in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- from .users import users_router
-
- app = FastAPI()
-
- app.include_router(users_router)
- ```
- """
- self.router.include_router(
- router,
- prefix=prefix,
- tags=tags,
- dependencies=dependencies,
- responses=responses,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- default_response_class=default_response_class,
- callbacks=callbacks,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def get(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP GET operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
-
- @app.get("/items/")
- def read_items():
- return [{"name": "Empanada"}, {"name": "Arepa"}]
- ```
- """
- return self.router.get(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def put(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP PUT operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
-
- @app.put("/items/{item_id}")
- def replace_item(item_id: str, item: Item):
- return {"message": "Item replaced", "id": item_id}
- ```
- """
- return self.router.put(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def post(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP POST operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
-
- @app.post("/items/")
- def create_item(item: Item):
- return {"message": "Item created"}
- ```
- """
- return self.router.post(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def delete(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP DELETE operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
-
- @app.delete("/items/{item_id}")
- def delete_item(item_id: str):
- return {"message": "Item deleted"}
- ```
- """
- return self.router.delete(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def options(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP OPTIONS operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
-
- @app.options("/items/")
- def get_item_options():
- return {"additions": ["Aji", "Guacamole"]}
- ```
- """
- return self.router.options(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def head(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP HEAD operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI, Response
-
- app = FastAPI()
-
- @app.head("/items/", status_code=204)
- def get_items_headers(response: Response):
- response.headers["X-Cat-Dog"] = "Alone in the world"
- ```
- """
- return self.router.head(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def patch(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP PATCH operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
-
- @app.patch("/items/")
- def update_item(item: Item):
- return {"message": "Item updated in place"}
- ```
- """
- return self.router.patch(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def trace(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[routing.APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP TRACE operation.
-
- ## Example
-
- ```python
- from fastapi import FastAPI
-
- app = FastAPI()
-
- @app.trace("/items/{item_id}")
- def trace_item(item_id: str):
- return None
- ```
- """
- return self.router.trace(
- path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def websocket_route(
- self, path: str, name: Union[str, None] = None
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.router.add_websocket_route(path, func, name=name)
- return func
-
- return decorator
-
- @deprecated(
- """
- on_event is deprecated, use lifespan event handlers instead.
-
- Read more about it in the
- [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
- """
- )
- def on_event(
- self,
- event_type: Annotated[
- str,
- Doc(
- """
- The type of event. `startup` or `shutdown`.
- """
- ),
- ],
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add an event handler for the application.
-
- `on_event` is deprecated, use `lifespan` event handlers instead.
-
- Read more about it in the
- [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated).
- """
- return self.router.on_event(event_type)
-
- def middleware(
- self,
- middleware_type: Annotated[
- str,
- Doc(
- """
- The type of middleware. Currently only supports `http`.
- """
- ),
- ],
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a middleware to the application.
-
- Read more about it in the
- [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/).
-
- ## Example
-
- ```python
- import time
- from typing import Awaitable, Callable
-
- from fastapi import FastAPI, Request, Response
-
- app = FastAPI()
-
-
- @app.middleware("http")
- async def add_process_time_header(
- request: Request, call_next: Callable[[Request], Awaitable[Response]]
- ) -> Response:
- start_time = time.time()
- response = await call_next(request)
- process_time = time.time() - start_time
- response.headers["X-Process-Time"] = str(process_time)
- return response
- ```
- """
-
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_middleware(BaseHTTPMiddleware, dispatch=func)
- return func
-
- return decorator
-
- def exception_handler(
- self,
- exc_class_or_status_code: Annotated[
- Union[int, type[Exception]],
- Doc(
- """
- The Exception class this would handle, or a status code.
- """
- ),
- ],
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add an exception handler to the app.
-
- Read more about it in the
- [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/).
-
- ## Example
-
- ```python
- from fastapi import FastAPI, Request
- from fastapi.responses import JSONResponse
-
-
- class UnicornException(Exception):
- def __init__(self, name: str):
- self.name = name
-
-
- app = FastAPI()
-
-
- @app.exception_handler(UnicornException)
- async def unicorn_exception_handler(request: Request, exc: UnicornException):
- return JSONResponse(
- status_code=418,
- content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
- )
- ```
- """
-
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_exception_handler(exc_class_or_status_code, func)
- return func
-
- return decorator
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/background.py b/backend/.venv/lib/python3.12/site-packages/fastapi/background.py
deleted file mode 100644
index 20803ba..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/background.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from typing import Annotated, Any, Callable
-
-from annotated_doc import Doc
-from starlette.background import BackgroundTasks as StarletteBackgroundTasks
-from typing_extensions import ParamSpec
-
-P = ParamSpec("P")
-
-
-class BackgroundTasks(StarletteBackgroundTasks):
- """
- A collection of background tasks that will be called after a response has been
- sent to the client.
-
- Read more about it in the
- [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/).
-
- ## Example
-
- ```python
- from fastapi import BackgroundTasks, FastAPI
-
- app = FastAPI()
-
-
- def write_notification(email: str, message=""):
- with open("log.txt", mode="w") as email_file:
- content = f"notification for {email}: {message}"
- email_file.write(content)
-
-
- @app.post("/send-notification/{email}")
- async def send_notification(email: str, background_tasks: BackgroundTasks):
- background_tasks.add_task(write_notification, email, message="some notification")
- return {"message": "Notification sent in the background"}
- ```
- """
-
- def add_task(
- self,
- func: Annotated[
- Callable[P, Any],
- Doc(
- """
- The function to call after the response is sent.
-
- It can be a regular `def` function or an `async def` function.
- """
- ),
- ],
- *args: P.args,
- **kwargs: P.kwargs,
- ) -> None:
- """
- Add a function to be called in the background after the response is sent.
-
- Read more about it in the
- [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/).
- """
- return super().add_task(func, *args, **kwargs)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/cli.py b/backend/.venv/lib/python3.12/site-packages/fastapi/cli.py
deleted file mode 100644
index 8d3301e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/cli.py
+++ /dev/null
@@ -1,13 +0,0 @@
-try:
- from fastapi_cli.cli import main as cli_main
-
-except ImportError: # pragma: no cover
- cli_main = None # type: ignore
-
-
-def main() -> None:
- if not cli_main: # type: ignore[truthy-function]
- message = 'To use the fastapi command, please install "fastapi[standard]":\n\n\tpip install "fastapi[standard]"\n'
- print(message)
- raise RuntimeError(message) # noqa: B904
- cli_main()
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/concurrency.py b/backend/.venv/lib/python3.12/site-packages/fastapi/concurrency.py
deleted file mode 100644
index 76a5a2e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/concurrency.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from collections.abc import AsyncGenerator
-from contextlib import AbstractContextManager
-from contextlib import asynccontextmanager as asynccontextmanager
-from typing import TypeVar
-
-import anyio.to_thread
-from anyio import CapacityLimiter
-from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa
-from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa
-from starlette.concurrency import ( # noqa
- run_until_first_complete as run_until_first_complete,
-)
-
-_T = TypeVar("_T")
-
-
-@asynccontextmanager
-async def contextmanager_in_threadpool(
- cm: AbstractContextManager[_T],
-) -> AsyncGenerator[_T, None]:
- # blocking __exit__ from running waiting on a free thread
- # can create race conditions/deadlocks if the context manager itself
- # has its own internal pool (e.g. a database connection pool)
- # to avoid this we let __exit__ run without a capacity limit
- # since we're creating a new limiter for each call, any non-zero limit
- # works (1 is arbitrary)
- exit_limiter = CapacityLimiter(1)
- try:
- yield await run_in_threadpool(cm.__enter__)
- except Exception as e:
- ok = bool(
- await anyio.to_thread.run_sync(
- cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter
- )
- )
- if not ok:
- raise e
- else:
- await anyio.to_thread.run_sync(
- cm.__exit__, None, None, None, limiter=exit_limiter
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/datastructures.py b/backend/.venv/lib/python3.12/site-packages/fastapi/datastructures.py
deleted file mode 100644
index 2bf5fdb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/datastructures.py
+++ /dev/null
@@ -1,183 +0,0 @@
-from collections.abc import Mapping
-from typing import (
- Annotated,
- Any,
- BinaryIO,
- Callable,
- Optional,
- TypeVar,
- cast,
-)
-
-from annotated_doc import Doc
-from pydantic import GetJsonSchemaHandler
-from starlette.datastructures import URL as URL # noqa: F401
-from starlette.datastructures import Address as Address # noqa: F401
-from starlette.datastructures import FormData as FormData # noqa: F401
-from starlette.datastructures import Headers as Headers # noqa: F401
-from starlette.datastructures import QueryParams as QueryParams # noqa: F401
-from starlette.datastructures import State as State # noqa: F401
-from starlette.datastructures import UploadFile as StarletteUploadFile
-
-
-class UploadFile(StarletteUploadFile):
- """
- A file uploaded in a request.
-
- Define it as a *path operation function* (or dependency) parameter.
-
- If you are using a regular `def` function, you can use the `upload_file.file`
- attribute to access the raw standard Python file (blocking, not async), useful and
- needed for non-async code.
-
- Read more about it in the
- [FastAPI docs for Request Files](https://fastapi.tiangolo.com/tutorial/request-files/).
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import FastAPI, File, UploadFile
-
- app = FastAPI()
-
-
- @app.post("/files/")
- async def create_file(file: Annotated[bytes, File()]):
- return {"file_size": len(file)}
-
-
- @app.post("/uploadfile/")
- async def create_upload_file(file: UploadFile):
- return {"filename": file.filename}
- ```
- """
-
- file: Annotated[
- BinaryIO,
- Doc("The standard Python file object (non-async)."),
- ]
- filename: Annotated[Optional[str], Doc("The original file name.")]
- size: Annotated[Optional[int], Doc("The size of the file in bytes.")]
- headers: Annotated[Headers, Doc("The headers of the request.")]
- content_type: Annotated[
- Optional[str], Doc("The content type of the request, from the headers.")
- ]
-
- async def write(
- self,
- data: Annotated[
- bytes,
- Doc(
- """
- The bytes to write to the file.
- """
- ),
- ],
- ) -> None:
- """
- Write some bytes to the file.
-
- You normally wouldn't use this from a file you read in a request.
-
- To be awaitable, compatible with async, this is run in threadpool.
- """
- return await super().write(data)
-
- async def read(
- self,
- size: Annotated[
- int,
- Doc(
- """
- The number of bytes to read from the file.
- """
- ),
- ] = -1,
- ) -> bytes:
- """
- Read some bytes from the file.
-
- To be awaitable, compatible with async, this is run in threadpool.
- """
- return await super().read(size)
-
- async def seek(
- self,
- offset: Annotated[
- int,
- Doc(
- """
- The position in bytes to seek to in the file.
- """
- ),
- ],
- ) -> None:
- """
- Move to a position in the file.
-
- Any next read or write will be done from that position.
-
- To be awaitable, compatible with async, this is run in threadpool.
- """
- return await super().seek(offset)
-
- async def close(self) -> None:
- """
- Close the file.
-
- To be awaitable, compatible with async, this is run in threadpool.
- """
- return await super().close()
-
- @classmethod
- def _validate(cls, __input_value: Any, _: Any) -> "UploadFile":
- if not isinstance(__input_value, StarletteUploadFile):
- raise ValueError(f"Expected UploadFile, received: {type(__input_value)}")
- return cast(UploadFile, __input_value)
-
- @classmethod
- def __get_pydantic_json_schema__(
- cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
- ) -> dict[str, Any]:
- return {"type": "string", "format": "binary"}
-
- @classmethod
- def __get_pydantic_core_schema__(
- cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]]
- ) -> Mapping[str, Any]:
- from ._compat.v2 import with_info_plain_validator_function
-
- return with_info_plain_validator_function(cls._validate)
-
-
-class DefaultPlaceholder:
- """
- You shouldn't use this class directly.
-
- It's used internally to recognize when a default value has been overwritten, even
- if the overridden default value was truthy.
- """
-
- def __init__(self, value: Any):
- self.value = value
-
- def __bool__(self) -> bool:
- return bool(self.value)
-
- def __eq__(self, o: object) -> bool:
- return isinstance(o, DefaultPlaceholder) and o.value == self.value
-
-
-DefaultType = TypeVar("DefaultType")
-
-
-def Default(value: DefaultType) -> DefaultType:
- """
- You shouldn't use this function directly.
-
- It's used internally to recognize when a default value has been overwritten, even
- if the overridden default value was truthy.
- """
- return DefaultPlaceholder(value) # type: ignore
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index faeedf5..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc
deleted file mode 100644
index fe421dd..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc
deleted file mode 100644
index 9a0d464..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/models.py b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/models.py
deleted file mode 100644
index 5839232..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/models.py
+++ /dev/null
@@ -1,193 +0,0 @@
-import inspect
-import sys
-from dataclasses import dataclass, field
-from functools import cached_property, partial
-from typing import Any, Callable, Optional, Union
-
-from fastapi._compat import ModelField
-from fastapi.security.base import SecurityBase
-from fastapi.types import DependencyCacheKey
-from typing_extensions import Literal
-
-if sys.version_info >= (3, 13): # pragma: no cover
- from inspect import iscoroutinefunction
-else: # pragma: no cover
- from asyncio import iscoroutinefunction
-
-
-def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any:
- if call is None:
- return call # pragma: no cover
- unwrapped = inspect.unwrap(_impartial(call))
- return unwrapped
-
-
-def _impartial(func: Callable[..., Any]) -> Callable[..., Any]:
- while isinstance(func, partial):
- func = func.func
- return func
-
-
-@dataclass
-class Dependant:
- path_params: list[ModelField] = field(default_factory=list)
- query_params: list[ModelField] = field(default_factory=list)
- header_params: list[ModelField] = field(default_factory=list)
- cookie_params: list[ModelField] = field(default_factory=list)
- body_params: list[ModelField] = field(default_factory=list)
- dependencies: list["Dependant"] = field(default_factory=list)
- name: Optional[str] = None
- call: Optional[Callable[..., Any]] = None
- request_param_name: Optional[str] = None
- websocket_param_name: Optional[str] = None
- http_connection_param_name: Optional[str] = None
- response_param_name: Optional[str] = None
- background_tasks_param_name: Optional[str] = None
- security_scopes_param_name: Optional[str] = None
- own_oauth_scopes: Optional[list[str]] = None
- parent_oauth_scopes: Optional[list[str]] = None
- use_cache: bool = True
- path: Optional[str] = None
- scope: Union[Literal["function", "request"], None] = None
-
- @cached_property
- def oauth_scopes(self) -> list[str]:
- scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else []
- # This doesn't use a set to preserve order, just in case
- for scope in self.own_oauth_scopes or []:
- if scope not in scopes:
- scopes.append(scope)
- return scopes
-
- @cached_property
- def cache_key(self) -> DependencyCacheKey:
- scopes_for_cache = (
- tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else ()
- )
- return (
- self.call,
- scopes_for_cache,
- self.computed_scope or "",
- )
-
- @cached_property
- def _uses_scopes(self) -> bool:
- if self.own_oauth_scopes:
- return True
- if self.security_scopes_param_name is not None:
- return True
- if self._is_security_scheme:
- return True
- for sub_dep in self.dependencies:
- if sub_dep._uses_scopes:
- return True
- return False
-
- @cached_property
- def _is_security_scheme(self) -> bool:
- if self.call is None:
- return False # pragma: no cover
- unwrapped = _unwrapped_call(self.call)
- return isinstance(unwrapped, SecurityBase)
-
- # Mainly to get the type of SecurityBase, but it's the same self.call
- @cached_property
- def _security_scheme(self) -> SecurityBase:
- unwrapped = _unwrapped_call(self.call)
- assert isinstance(unwrapped, SecurityBase)
- return unwrapped
-
- @cached_property
- def _security_dependencies(self) -> list["Dependant"]:
- security_deps = [dep for dep in self.dependencies if dep._is_security_scheme]
- return security_deps
-
- @cached_property
- def is_gen_callable(self) -> bool:
- if self.call is None:
- return False # pragma: no cover
- if inspect.isgeneratorfunction(
- _impartial(self.call)
- ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
- return True
- if inspect.isclass(_unwrapped_call(self.call)):
- return False
- dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
- if dunder_call is None:
- return False # pragma: no cover
- if inspect.isgeneratorfunction(
- _impartial(dunder_call)
- ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):
- return True
- dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
- if dunder_unwrapped_call is None:
- return False # pragma: no cover
- if inspect.isgeneratorfunction(
- _impartial(dunder_unwrapped_call)
- ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)):
- return True
- return False
-
- @cached_property
- def is_async_gen_callable(self) -> bool:
- if self.call is None:
- return False # pragma: no cover
- if inspect.isasyncgenfunction(
- _impartial(self.call)
- ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):
- return True
- if inspect.isclass(_unwrapped_call(self.call)):
- return False
- dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
- if dunder_call is None:
- return False # pragma: no cover
- if inspect.isasyncgenfunction(
- _impartial(dunder_call)
- ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):
- return True
- dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
- if dunder_unwrapped_call is None:
- return False # pragma: no cover
- if inspect.isasyncgenfunction(
- _impartial(dunder_unwrapped_call)
- ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)):
- return True
- return False
-
- @cached_property
- def is_coroutine_callable(self) -> bool:
- if self.call is None:
- return False # pragma: no cover
- if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(
- _impartial(self.call)
- ):
- return True
- if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction(
- _unwrapped_call(self.call)
- ):
- return True
- if inspect.isclass(_unwrapped_call(self.call)):
- return False
- dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
- if dunder_call is None:
- return False # pragma: no cover
- if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(
- _unwrapped_call(dunder_call)
- ):
- return True
- dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
- if dunder_unwrapped_call is None:
- return False # pragma: no cover
- if iscoroutinefunction(
- _impartial(dunder_unwrapped_call)
- ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)):
- return True
- return False
-
- @cached_property
- def computed_scope(self) -> Union[str, None]:
- if self.scope:
- return self.scope
- if self.is_gen_callable or self.is_async_gen_callable:
- return "request"
- return None
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py b/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py
deleted file mode 100644
index 45e1ff3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py
+++ /dev/null
@@ -1,1021 +0,0 @@
-import dataclasses
-import inspect
-import sys
-from collections.abc import Coroutine, Mapping, Sequence
-from contextlib import AsyncExitStack, contextmanager
-from copy import copy, deepcopy
-from dataclasses import dataclass
-from typing import (
- Annotated,
- Any,
- Callable,
- ForwardRef,
- Optional,
- Union,
- cast,
-)
-
-import anyio
-from fastapi import params
-from fastapi._compat import (
- ModelField,
- RequiredParam,
- Undefined,
- _regenerate_error_with_loc,
- copy_field_info,
- create_body_model,
- evaluate_forwardref,
- field_annotation_is_scalar,
- get_cached_model_fields,
- get_missing_field_error,
- is_bytes_field,
- is_bytes_sequence_field,
- is_scalar_field,
- is_scalar_sequence_field,
- is_sequence_field,
- is_uploadfile_or_nonable_uploadfile_annotation,
- is_uploadfile_sequence_annotation,
- lenient_issubclass,
- sequence_types,
- serialize_sequence_value,
- value_is_sequence,
-)
-from fastapi.background import BackgroundTasks
-from fastapi.concurrency import (
- asynccontextmanager,
- contextmanager_in_threadpool,
-)
-from fastapi.dependencies.models import Dependant
-from fastapi.exceptions import DependencyScopeError
-from fastapi.logger import logger
-from fastapi.security.oauth2 import SecurityScopes
-from fastapi.types import DependencyCacheKey
-from fastapi.utils import create_model_field, get_path_param_names
-from pydantic import BaseModel
-from pydantic.fields import FieldInfo
-from starlette.background import BackgroundTasks as StarletteBackgroundTasks
-from starlette.concurrency import run_in_threadpool
-from starlette.datastructures import (
- FormData,
- Headers,
- ImmutableMultiDict,
- QueryParams,
- UploadFile,
-)
-from starlette.requests import HTTPConnection, Request
-from starlette.responses import Response
-from starlette.websockets import WebSocket
-from typing_extensions import Literal, get_args, get_origin
-
-multipart_not_installed_error = (
- 'Form data requires "python-multipart" to be installed. \n'
- 'You can install "python-multipart" with: \n\n'
- "pip install python-multipart\n"
-)
-multipart_incorrect_install_error = (
- 'Form data requires "python-multipart" to be installed. '
- 'It seems you installed "multipart" instead. \n'
- 'You can remove "multipart" with: \n\n'
- "pip uninstall multipart\n\n"
- 'And then install "python-multipart" with: \n\n'
- "pip install python-multipart\n"
-)
-
-
-def ensure_multipart_is_installed() -> None:
- try:
- from python_multipart import __version__
-
- # Import an attribute that can be mocked/deleted in testing
- assert __version__ > "0.0.12"
- except (ImportError, AssertionError):
- try:
- # __version__ is available in both multiparts, and can be mocked
- from multipart import __version__ # type: ignore[no-redef,import-untyped]
-
- assert __version__
- try:
- # parse_options_header is only available in the right multipart
- from multipart.multipart import ( # type: ignore[import-untyped]
- parse_options_header,
- )
-
- assert parse_options_header
- except ImportError:
- logger.error(multipart_incorrect_install_error)
- raise RuntimeError(multipart_incorrect_install_error) from None
- except ImportError:
- logger.error(multipart_not_installed_error)
- raise RuntimeError(multipart_not_installed_error) from None
-
-
-def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
- assert callable(depends.dependency), (
- "A parameter-less dependency must have a callable dependency"
- )
- own_oauth_scopes: list[str] = []
- if isinstance(depends, params.Security) and depends.scopes:
- own_oauth_scopes.extend(depends.scopes)
- return get_dependant(
- path=path,
- call=depends.dependency,
- scope=depends.scope,
- own_oauth_scopes=own_oauth_scopes,
- )
-
-
-def get_flat_dependant(
- dependant: Dependant,
- *,
- skip_repeats: bool = False,
- visited: Optional[list[DependencyCacheKey]] = None,
- parent_oauth_scopes: Optional[list[str]] = None,
-) -> Dependant:
- if visited is None:
- visited = []
- visited.append(dependant.cache_key)
- use_parent_oauth_scopes = (parent_oauth_scopes or []) + (
- dependant.oauth_scopes or []
- )
-
- flat_dependant = Dependant(
- path_params=dependant.path_params.copy(),
- query_params=dependant.query_params.copy(),
- header_params=dependant.header_params.copy(),
- cookie_params=dependant.cookie_params.copy(),
- body_params=dependant.body_params.copy(),
- name=dependant.name,
- call=dependant.call,
- request_param_name=dependant.request_param_name,
- websocket_param_name=dependant.websocket_param_name,
- http_connection_param_name=dependant.http_connection_param_name,
- response_param_name=dependant.response_param_name,
- background_tasks_param_name=dependant.background_tasks_param_name,
- security_scopes_param_name=dependant.security_scopes_param_name,
- own_oauth_scopes=dependant.own_oauth_scopes,
- parent_oauth_scopes=use_parent_oauth_scopes,
- use_cache=dependant.use_cache,
- path=dependant.path,
- scope=dependant.scope,
- )
- for sub_dependant in dependant.dependencies:
- if skip_repeats and sub_dependant.cache_key in visited:
- continue
- flat_sub = get_flat_dependant(
- sub_dependant,
- skip_repeats=skip_repeats,
- visited=visited,
- parent_oauth_scopes=flat_dependant.oauth_scopes,
- )
- flat_dependant.dependencies.append(flat_sub)
- flat_dependant.path_params.extend(flat_sub.path_params)
- flat_dependant.query_params.extend(flat_sub.query_params)
- flat_dependant.header_params.extend(flat_sub.header_params)
- flat_dependant.cookie_params.extend(flat_sub.cookie_params)
- flat_dependant.body_params.extend(flat_sub.body_params)
- flat_dependant.dependencies.extend(flat_sub.dependencies)
-
- return flat_dependant
-
-
-def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
- if not fields:
- return fields
- first_field = fields[0]
- if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
- fields_to_extract = get_cached_model_fields(first_field.type_)
- return fields_to_extract
- return fields
-
-
-def get_flat_params(dependant: Dependant) -> list[ModelField]:
- flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
- path_params = _get_flat_fields_from_params(flat_dependant.path_params)
- query_params = _get_flat_fields_from_params(flat_dependant.query_params)
- header_params = _get_flat_fields_from_params(flat_dependant.header_params)
- cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
- return path_params + query_params + header_params + cookie_params
-
-
-def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
- if sys.version_info >= (3, 10):
- try:
- signature = inspect.signature(call, eval_str=True)
- except NameError:
- # Handle type annotations with if TYPE_CHECKING, not used by FastAPI
- # e.g. dependency return types
- signature = inspect.signature(call)
- else:
- signature = inspect.signature(call)
- return signature
-
-
-def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
- signature = _get_signature(call)
- unwrapped = inspect.unwrap(call)
- globalns = getattr(unwrapped, "__globals__", {})
- typed_params = [
- inspect.Parameter(
- name=param.name,
- kind=param.kind,
- default=param.default,
- annotation=get_typed_annotation(param.annotation, globalns),
- )
- for param in signature.parameters.values()
- ]
- typed_signature = inspect.Signature(typed_params)
- return typed_signature
-
-
-def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
- if isinstance(annotation, str):
- annotation = ForwardRef(annotation)
- annotation = evaluate_forwardref(annotation, globalns, globalns)
- if annotation is type(None):
- return None
- return annotation
-
-
-def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
- signature = _get_signature(call)
- unwrapped = inspect.unwrap(call)
- annotation = signature.return_annotation
-
- if annotation is inspect.Signature.empty:
- return None
-
- globalns = getattr(unwrapped, "__globals__", {})
- return get_typed_annotation(annotation, globalns)
-
-
-def get_dependant(
- *,
- path: str,
- call: Callable[..., Any],
- name: Optional[str] = None,
- own_oauth_scopes: Optional[list[str]] = None,
- parent_oauth_scopes: Optional[list[str]] = None,
- use_cache: bool = True,
- scope: Union[Literal["function", "request"], None] = None,
-) -> Dependant:
- dependant = Dependant(
- call=call,
- name=name,
- path=path,
- use_cache=use_cache,
- scope=scope,
- own_oauth_scopes=own_oauth_scopes,
- parent_oauth_scopes=parent_oauth_scopes,
- )
- current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])
- path_param_names = get_path_param_names(path)
- endpoint_signature = get_typed_signature(call)
- signature_params = endpoint_signature.parameters
- for param_name, param in signature_params.items():
- is_path_param = param_name in path_param_names
- param_details = analyze_param(
- param_name=param_name,
- annotation=param.annotation,
- value=param.default,
- is_path_param=is_path_param,
- )
- if param_details.depends is not None:
- assert param_details.depends.dependency
- if (
- (dependant.is_gen_callable or dependant.is_async_gen_callable)
- and dependant.computed_scope == "request"
- and param_details.depends.scope == "function"
- ):
- assert dependant.call
- raise DependencyScopeError(
- f'The dependency "{dependant.call.__name__}" has a scope of '
- '"request", it cannot depend on dependencies with scope "function".'
- )
- sub_own_oauth_scopes: list[str] = []
- if isinstance(param_details.depends, params.Security):
- if param_details.depends.scopes:
- sub_own_oauth_scopes = list(param_details.depends.scopes)
- sub_dependant = get_dependant(
- path=path,
- call=param_details.depends.dependency,
- name=param_name,
- own_oauth_scopes=sub_own_oauth_scopes,
- parent_oauth_scopes=current_scopes,
- use_cache=param_details.depends.use_cache,
- scope=param_details.depends.scope,
- )
- dependant.dependencies.append(sub_dependant)
- continue
- if add_non_field_param_to_dependency(
- param_name=param_name,
- type_annotation=param_details.type_annotation,
- dependant=dependant,
- ):
- assert param_details.field is None, (
- f"Cannot specify multiple FastAPI annotations for {param_name!r}"
- )
- continue
- assert param_details.field is not None
- if isinstance(param_details.field.field_info, params.Body):
- dependant.body_params.append(param_details.field)
- else:
- add_param_to_fields(field=param_details.field, dependant=dependant)
- return dependant
-
-
-def add_non_field_param_to_dependency(
- *, param_name: str, type_annotation: Any, dependant: Dependant
-) -> Optional[bool]:
- if lenient_issubclass(type_annotation, Request):
- dependant.request_param_name = param_name
- return True
- elif lenient_issubclass(type_annotation, WebSocket):
- dependant.websocket_param_name = param_name
- return True
- elif lenient_issubclass(type_annotation, HTTPConnection):
- dependant.http_connection_param_name = param_name
- return True
- elif lenient_issubclass(type_annotation, Response):
- dependant.response_param_name = param_name
- return True
- elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):
- dependant.background_tasks_param_name = param_name
- return True
- elif lenient_issubclass(type_annotation, SecurityScopes):
- dependant.security_scopes_param_name = param_name
- return True
- return None
-
-
-@dataclass
-class ParamDetails:
- type_annotation: Any
- depends: Optional[params.Depends]
- field: Optional[ModelField]
-
-
-def analyze_param(
- *,
- param_name: str,
- annotation: Any,
- value: Any,
- is_path_param: bool,
-) -> ParamDetails:
- field_info = None
- depends = None
- type_annotation: Any = Any
- use_annotation: Any = Any
- if annotation is not inspect.Signature.empty:
- use_annotation = annotation
- type_annotation = annotation
- # Extract Annotated info
- if get_origin(use_annotation) is Annotated:
- annotated_args = get_args(annotation)
- type_annotation = annotated_args[0]
- fastapi_annotations = [
- arg
- for arg in annotated_args[1:]
- if isinstance(arg, (FieldInfo, params.Depends))
- ]
- fastapi_specific_annotations = [
- arg
- for arg in fastapi_annotations
- if isinstance(
- arg,
- (
- params.Param,
- params.Body,
- params.Depends,
- ),
- )
- ]
- if fastapi_specific_annotations:
- fastapi_annotation: Union[FieldInfo, params.Depends, None] = (
- fastapi_specific_annotations[-1]
- )
- else:
- fastapi_annotation = None
- # Set default for Annotated FieldInfo
- if isinstance(fastapi_annotation, FieldInfo):
- # Copy `field_info` because we mutate `field_info.default` below.
- field_info = copy_field_info(
- field_info=fastapi_annotation, # type: ignore[arg-type]
- annotation=use_annotation,
- )
- assert (
- field_info.default == Undefined or field_info.default == RequiredParam
- ), (
- f"`{field_info.__class__.__name__}` default value cannot be set in"
- f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
- )
- if value is not inspect.Signature.empty:
- assert not is_path_param, "Path parameters cannot have default values"
- field_info.default = value
- else:
- field_info.default = RequiredParam
- # Get Annotated Depends
- elif isinstance(fastapi_annotation, params.Depends):
- depends = fastapi_annotation
- # Get Depends from default value
- if isinstance(value, params.Depends):
- assert depends is None, (
- "Cannot specify `Depends` in `Annotated` and default value"
- f" together for {param_name!r}"
- )
- assert field_info is None, (
- "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a"
- f" default value together for {param_name!r}"
- )
- depends = value
- # Get FieldInfo from default value
- elif isinstance(value, FieldInfo):
- assert field_info is None, (
- "Cannot specify FastAPI annotations in `Annotated` and default value"
- f" together for {param_name!r}"
- )
- field_info = value # type: ignore[assignment]
- if isinstance(field_info, FieldInfo):
- field_info.annotation = type_annotation
-
- # Get Depends from type annotation
- if depends is not None and depends.dependency is None:
- # Copy `depends` before mutating it
- depends = copy(depends)
- depends = dataclasses.replace(depends, dependency=type_annotation)
-
- # Handle non-param type annotations like Request
- if lenient_issubclass(
- type_annotation,
- (
- Request,
- WebSocket,
- HTTPConnection,
- Response,
- StarletteBackgroundTasks,
- SecurityScopes,
- ),
- ):
- assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
- assert field_info is None, (
- f"Cannot specify FastAPI annotation for type {type_annotation!r}"
- )
- # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
- elif field_info is None and depends is None:
- default_value = value if value is not inspect.Signature.empty else RequiredParam
- if is_path_param:
- # We might check here that `default_value is RequiredParam`, but the fact is that the same
- # parameter might sometimes be a path parameter and sometimes not. See
- # `tests/test_infer_param_optionality.py` for an example.
- field_info = params.Path(annotation=use_annotation)
- elif is_uploadfile_or_nonable_uploadfile_annotation(
- type_annotation
- ) or is_uploadfile_sequence_annotation(type_annotation):
- field_info = params.File(annotation=use_annotation, default=default_value)
- elif not field_annotation_is_scalar(annotation=type_annotation):
- field_info = params.Body(annotation=use_annotation, default=default_value)
- else:
- field_info = params.Query(annotation=use_annotation, default=default_value)
-
- field = None
- # It's a field_info, not a dependency
- if field_info is not None:
- # Handle field_info.in_
- if is_path_param:
- assert isinstance(field_info, params.Path), (
- f"Cannot use `{field_info.__class__.__name__}` for path param"
- f" {param_name!r}"
- )
- elif (
- isinstance(field_info, params.Param)
- and getattr(field_info, "in_", None) is None
- ):
- field_info.in_ = params.ParamTypes.query
- use_annotation_from_field_info = use_annotation
- if isinstance(field_info, params.Form):
- ensure_multipart_is_installed()
- if not field_info.alias and getattr(field_info, "convert_underscores", None):
- alias = param_name.replace("_", "-")
- else:
- alias = field_info.alias or param_name
- field_info.alias = alias
- field = create_model_field(
- name=param_name,
- type_=use_annotation_from_field_info,
- default=field_info.default,
- alias=alias,
- required=field_info.default in (RequiredParam, Undefined),
- field_info=field_info,
- )
- if is_path_param:
- assert is_scalar_field(field=field), (
- "Path params must be of one of the supported types"
- )
- elif isinstance(field_info, params.Query):
- assert (
- is_scalar_field(field)
- or is_scalar_sequence_field(field)
- or (
- lenient_issubclass(field.type_, BaseModel)
- # For Pydantic v1
- and getattr(field, "shape", 1) == 1
- )
- )
-
- return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
-
-
-def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
- field_info = field.field_info
- field_info_in = getattr(field_info, "in_", None)
- if field_info_in == params.ParamTypes.path:
- dependant.path_params.append(field)
- elif field_info_in == params.ParamTypes.query:
- dependant.query_params.append(field)
- elif field_info_in == params.ParamTypes.header:
- dependant.header_params.append(field)
- else:
- assert field_info_in == params.ParamTypes.cookie, (
- f"non-body parameters must be in path, query, header or cookie: {field.name}"
- )
- dependant.cookie_params.append(field)
-
-
-async def _solve_generator(
- *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]
-) -> Any:
- assert dependant.call
- if dependant.is_async_gen_callable:
- cm = asynccontextmanager(dependant.call)(**sub_values)
- elif dependant.is_gen_callable:
- cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))
- return await stack.enter_async_context(cm)
-
-
-@dataclass
-class SolvedDependency:
- values: dict[str, Any]
- errors: list[Any]
- background_tasks: Optional[StarletteBackgroundTasks]
- response: Response
- dependency_cache: dict[DependencyCacheKey, Any]
-
-
-async def solve_dependencies(
- *,
- request: Union[Request, WebSocket],
- dependant: Dependant,
- body: Optional[Union[dict[str, Any], FormData]] = None,
- background_tasks: Optional[StarletteBackgroundTasks] = None,
- response: Optional[Response] = None,
- dependency_overrides_provider: Optional[Any] = None,
- dependency_cache: Optional[dict[DependencyCacheKey, Any]] = None,
- # TODO: remove this parameter later, no longer used, not removing it yet as some
- # people might be monkey patching this function (although that's not supported)
- async_exit_stack: AsyncExitStack,
- embed_body_fields: bool,
-) -> SolvedDependency:
- request_astack = request.scope.get("fastapi_inner_astack")
- assert isinstance(request_astack, AsyncExitStack), (
- "fastapi_inner_astack not found in request scope"
- )
- function_astack = request.scope.get("fastapi_function_astack")
- assert isinstance(function_astack, AsyncExitStack), (
- "fastapi_function_astack not found in request scope"
- )
- values: dict[str, Any] = {}
- errors: list[Any] = []
- if response is None:
- response = Response()
- del response.headers["content-length"]
- response.status_code = None # type: ignore
- if dependency_cache is None:
- dependency_cache = {}
- for sub_dependant in dependant.dependencies:
- sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
- call = sub_dependant.call
- use_sub_dependant = sub_dependant
- if (
- dependency_overrides_provider
- and dependency_overrides_provider.dependency_overrides
- ):
- original_call = sub_dependant.call
- call = getattr(
- dependency_overrides_provider, "dependency_overrides", {}
- ).get(original_call, original_call)
- use_path: str = sub_dependant.path # type: ignore
- use_sub_dependant = get_dependant(
- path=use_path,
- call=call,
- name=sub_dependant.name,
- parent_oauth_scopes=sub_dependant.oauth_scopes,
- scope=sub_dependant.scope,
- )
-
- solved_result = await solve_dependencies(
- request=request,
- dependant=use_sub_dependant,
- body=body,
- background_tasks=background_tasks,
- response=response,
- dependency_overrides_provider=dependency_overrides_provider,
- dependency_cache=dependency_cache,
- async_exit_stack=async_exit_stack,
- embed_body_fields=embed_body_fields,
- )
- background_tasks = solved_result.background_tasks
- if solved_result.errors:
- errors.extend(solved_result.errors)
- continue
- if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
- solved = dependency_cache[sub_dependant.cache_key]
- elif (
- use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable
- ):
- use_astack = request_astack
- if sub_dependant.scope == "function":
- use_astack = function_astack
- solved = await _solve_generator(
- dependant=use_sub_dependant,
- stack=use_astack,
- sub_values=solved_result.values,
- )
- elif use_sub_dependant.is_coroutine_callable:
- solved = await call(**solved_result.values)
- else:
- solved = await run_in_threadpool(call, **solved_result.values)
- if sub_dependant.name is not None:
- values[sub_dependant.name] = solved
- if sub_dependant.cache_key not in dependency_cache:
- dependency_cache[sub_dependant.cache_key] = solved
- path_values, path_errors = request_params_to_args(
- dependant.path_params, request.path_params
- )
- query_values, query_errors = request_params_to_args(
- dependant.query_params, request.query_params
- )
- header_values, header_errors = request_params_to_args(
- dependant.header_params, request.headers
- )
- cookie_values, cookie_errors = request_params_to_args(
- dependant.cookie_params, request.cookies
- )
- values.update(path_values)
- values.update(query_values)
- values.update(header_values)
- values.update(cookie_values)
- errors += path_errors + query_errors + header_errors + cookie_errors
- if dependant.body_params:
- (
- body_values,
- body_errors,
- ) = await request_body_to_args( # body_params checked above
- body_fields=dependant.body_params,
- received_body=body,
- embed_body_fields=embed_body_fields,
- )
- values.update(body_values)
- errors.extend(body_errors)
- if dependant.http_connection_param_name:
- values[dependant.http_connection_param_name] = request
- if dependant.request_param_name and isinstance(request, Request):
- values[dependant.request_param_name] = request
- elif dependant.websocket_param_name and isinstance(request, WebSocket):
- values[dependant.websocket_param_name] = request
- if dependant.background_tasks_param_name:
- if background_tasks is None:
- background_tasks = BackgroundTasks()
- values[dependant.background_tasks_param_name] = background_tasks
- if dependant.response_param_name:
- values[dependant.response_param_name] = response
- if dependant.security_scopes_param_name:
- values[dependant.security_scopes_param_name] = SecurityScopes(
- scopes=dependant.oauth_scopes
- )
- return SolvedDependency(
- values=values,
- errors=errors,
- background_tasks=background_tasks,
- response=response,
- dependency_cache=dependency_cache,
- )
-
-
-def _validate_value_with_model_field(
- *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
-) -> tuple[Any, list[Any]]:
- if value is None:
- if field.required:
- return None, [get_missing_field_error(loc=loc)]
- else:
- return deepcopy(field.default), []
- v_, errors_ = field.validate(value, values, loc=loc)
- if isinstance(errors_, list):
- new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
- return None, new_errors
- else:
- return v_, []
-
-
-def _get_multidict_value(
- field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None
-) -> Any:
- alias = alias or get_validation_alias(field)
- if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
- value = values.getlist(alias)
- else:
- value = values.get(alias, None)
- if (
- value is None
- or (
- isinstance(field.field_info, params.Form)
- and isinstance(value, str) # For type checks
- and value == ""
- )
- or (is_sequence_field(field) and len(value) == 0)
- ):
- if field.required:
- return
- else:
- return deepcopy(field.default)
- return value
-
-
-def request_params_to_args(
- fields: Sequence[ModelField],
- received_params: Union[Mapping[str, Any], QueryParams, Headers],
-) -> tuple[dict[str, Any], list[Any]]:
- values: dict[str, Any] = {}
- errors: list[dict[str, Any]] = []
-
- if not fields:
- return values, errors
-
- first_field = fields[0]
- fields_to_extract = fields
- single_not_embedded_field = False
- default_convert_underscores = True
- if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
- fields_to_extract = get_cached_model_fields(first_field.type_)
- single_not_embedded_field = True
- # If headers are in a Pydantic model, the way to disable convert_underscores
- # would be with Header(convert_underscores=False) at the Pydantic model level
- default_convert_underscores = getattr(
- first_field.field_info, "convert_underscores", True
- )
-
- params_to_process: dict[str, Any] = {}
-
- processed_keys = set()
-
- for field in fields_to_extract:
- alias = None
- if isinstance(received_params, Headers):
- # Handle fields extracted from a Pydantic Model for a header, each field
- # doesn't have a FieldInfo of type Header with the default convert_underscores=True
- convert_underscores = getattr(
- field.field_info, "convert_underscores", default_convert_underscores
- )
- if convert_underscores:
- alias = get_validation_alias(field)
- if alias == field.name:
- alias = alias.replace("_", "-")
- value = _get_multidict_value(field, received_params, alias=alias)
- if value is not None:
- params_to_process[get_validation_alias(field)] = value
- processed_keys.add(alias or get_validation_alias(field))
-
- for key in received_params.keys():
- if key not in processed_keys:
- if hasattr(received_params, "getlist"):
- value = received_params.getlist(key)
- if isinstance(value, list) and (len(value) == 1):
- params_to_process[key] = value[0]
- else:
- params_to_process[key] = value
- else:
- params_to_process[key] = received_params.get(key)
-
- if single_not_embedded_field:
- field_info = first_field.field_info
- assert isinstance(field_info, params.Param), (
- "Params must be subclasses of Param"
- )
- loc: tuple[str, ...] = (field_info.in_.value,)
- v_, errors_ = _validate_value_with_model_field(
- field=first_field, value=params_to_process, values=values, loc=loc
- )
- return {first_field.name: v_}, errors_
-
- for field in fields:
- value = _get_multidict_value(field, received_params)
- field_info = field.field_info
- assert isinstance(field_info, params.Param), (
- "Params must be subclasses of Param"
- )
- loc = (field_info.in_.value, get_validation_alias(field))
- v_, errors_ = _validate_value_with_model_field(
- field=field, value=value, values=values, loc=loc
- )
- if errors_:
- errors.extend(errors_)
- else:
- values[field.name] = v_
- return values, errors
-
-
-def is_union_of_base_models(field_type: Any) -> bool:
- """Check if field type is a Union where all members are BaseModel subclasses."""
- from fastapi.types import UnionType
-
- origin = get_origin(field_type)
-
- # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)
- if origin is not Union and origin is not UnionType:
- return False
-
- union_args = get_args(field_type)
-
- for arg in union_args:
- if not lenient_issubclass(arg, BaseModel):
- return False
-
- return True
-
-
-def _should_embed_body_fields(fields: list[ModelField]) -> bool:
- if not fields:
- return False
- # More than one dependency could have the same field, it would show up as multiple
- # fields but it's the same one, so count them by name
- body_param_names_set = {field.name for field in fields}
- # A top level field has to be a single field, not multiple
- if len(body_param_names_set) > 1:
- return True
- first_field = fields[0]
- # If it explicitly specifies it is embedded, it has to be embedded
- if getattr(first_field.field_info, "embed", None):
- return True
- # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level
- # otherwise it has to be embedded, so that the key value pair can be extracted
- if (
- isinstance(first_field.field_info, params.Form)
- and not lenient_issubclass(first_field.type_, BaseModel)
- and not is_union_of_base_models(first_field.type_)
- ):
- return True
- return False
-
-
-async def _extract_form_body(
- body_fields: list[ModelField],
- received_body: FormData,
-) -> dict[str, Any]:
- values = {}
-
- for field in body_fields:
- value = _get_multidict_value(field, received_body)
- field_info = field.field_info
- if (
- isinstance(field_info, params.File)
- and is_bytes_field(field)
- and isinstance(value, UploadFile)
- ):
- value = await value.read()
- elif (
- is_bytes_sequence_field(field)
- and isinstance(field_info, params.File)
- and value_is_sequence(value)
- ):
- # For types
- assert isinstance(value, sequence_types)
- results: list[Union[bytes, str]] = []
-
- async def process_fn(
- fn: Callable[[], Coroutine[Any, Any, Any]],
- ) -> None:
- result = await fn()
- results.append(result) # noqa: B023
-
- async with anyio.create_task_group() as tg:
- for sub_value in value:
- tg.start_soon(process_fn, sub_value.read)
- value = serialize_sequence_value(field=field, value=results)
- if value is not None:
- values[get_validation_alias(field)] = value
- field_aliases = {get_validation_alias(field) for field in body_fields}
- for key in received_body.keys():
- if key not in field_aliases:
- param_values = received_body.getlist(key)
- if len(param_values) == 1:
- values[key] = param_values[0]
- else:
- values[key] = param_values
- return values
-
-
-async def request_body_to_args(
- body_fields: list[ModelField],
- received_body: Optional[Union[dict[str, Any], FormData]],
- embed_body_fields: bool,
-) -> tuple[dict[str, Any], list[dict[str, Any]]]:
- values: dict[str, Any] = {}
- errors: list[dict[str, Any]] = []
- assert body_fields, "request_body_to_args() should be called with fields"
- single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields
- first_field = body_fields[0]
- body_to_process = received_body
-
- fields_to_extract: list[ModelField] = body_fields
-
- if (
- single_not_embedded_field
- and lenient_issubclass(first_field.type_, BaseModel)
- and isinstance(received_body, FormData)
- ):
- fields_to_extract = get_cached_model_fields(first_field.type_)
-
- if isinstance(received_body, FormData):
- body_to_process = await _extract_form_body(fields_to_extract, received_body)
-
- if single_not_embedded_field:
- loc: tuple[str, ...] = ("body",)
- v_, errors_ = _validate_value_with_model_field(
- field=first_field, value=body_to_process, values=values, loc=loc
- )
- return {first_field.name: v_}, errors_
- for field in body_fields:
- loc = ("body", get_validation_alias(field))
- value: Optional[Any] = None
- if body_to_process is not None:
- try:
- value = body_to_process.get(get_validation_alias(field))
- # If the received body is a list, not a dict
- except AttributeError:
- errors.append(get_missing_field_error(loc))
- continue
- v_, errors_ = _validate_value_with_model_field(
- field=field, value=value, values=values, loc=loc
- )
- if errors_:
- errors.extend(errors_)
- else:
- values[field.name] = v_
- return values, errors
-
-
-def get_body_field(
- *, flat_dependant: Dependant, name: str, embed_body_fields: bool
-) -> Optional[ModelField]:
- """
- Get a ModelField representing the request body for a path operation, combining
- all body parameters into a single field if necessary.
-
- Used to check if it's form data (with `isinstance(body_field, params.Form)`)
- or JSON and to generate the JSON Schema for a request body.
-
- This is **not** used to validate/parse the request body, that's done with each
- individual body parameter.
- """
- if not flat_dependant.body_params:
- return None
- first_param = flat_dependant.body_params[0]
- if not embed_body_fields:
- return first_param
- model_name = "Body_" + name
- BodyModel = create_body_model(
- fields=flat_dependant.body_params, model_name=model_name
- )
- required = any(True for f in flat_dependant.body_params if f.required)
- BodyFieldInfo_kwargs: dict[str, Any] = {
- "annotation": BodyModel,
- "alias": "body",
- }
- if not required:
- BodyFieldInfo_kwargs["default"] = None
- if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
- BodyFieldInfo: type[params.Body] = params.File
- elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
- BodyFieldInfo = params.Form
- else:
- BodyFieldInfo = params.Body
-
- body_param_media_types = [
- f.field_info.media_type
- for f in flat_dependant.body_params
- if isinstance(f.field_info, params.Body)
- ]
- if len(set(body_param_media_types)) == 1:
- BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
- final_field = create_model_field(
- name="body",
- type_=BodyModel,
- required=required,
- alias="body",
- field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
- )
- return final_field
-
-
-def get_validation_alias(field: ModelField) -> str:
- va = getattr(field, "validation_alias", None)
- return va or field.alias
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/encoders.py b/backend/.venv/lib/python3.12/site-packages/fastapi/encoders.py
deleted file mode 100644
index e8610c9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/encoders.py
+++ /dev/null
@@ -1,346 +0,0 @@
-import dataclasses
-import datetime
-from collections import defaultdict, deque
-from decimal import Decimal
-from enum import Enum
-from ipaddress import (
- IPv4Address,
- IPv4Interface,
- IPv4Network,
- IPv6Address,
- IPv6Interface,
- IPv6Network,
-)
-from pathlib import Path, PurePath
-from re import Pattern
-from types import GeneratorType
-from typing import Annotated, Any, Callable, Optional, Union
-from uuid import UUID
-
-from annotated_doc import Doc
-from fastapi.exceptions import PydanticV1NotSupportedError
-from fastapi.types import IncEx
-from pydantic import BaseModel
-from pydantic.color import Color
-from pydantic.networks import AnyUrl, NameEmail
-from pydantic.types import SecretBytes, SecretStr
-from pydantic_core import PydanticUndefinedType
-
-from ._compat import (
- Url,
- is_pydantic_v1_model_instance,
-)
-
-
-# Taken from Pydantic v1 as is
-def isoformat(o: Union[datetime.date, datetime.time]) -> str:
- return o.isoformat()
-
-
-# Adapted from Pydantic v1
-# TODO: pv2 should this return strings instead?
-def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
- """
- Encodes a Decimal as int if there's no exponent, otherwise float
-
- This is useful when we use ConstrainedDecimal to represent Numeric(x,0)
- where an integer (but not int typed) is used. Encoding this as a float
- results in failed round-tripping between encode and parse.
- Our Id type is a prime example of this.
-
- >>> decimal_encoder(Decimal("1.0"))
- 1.0
-
- >>> decimal_encoder(Decimal("1"))
- 1
-
- >>> decimal_encoder(Decimal("NaN"))
- nan
- """
- exponent = dec_value.as_tuple().exponent
- if isinstance(exponent, int) and exponent >= 0:
- return int(dec_value)
- else:
- return float(dec_value)
-
-
-ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
- bytes: lambda o: o.decode(),
- Color: str,
- datetime.date: isoformat,
- datetime.datetime: isoformat,
- datetime.time: isoformat,
- datetime.timedelta: lambda td: td.total_seconds(),
- Decimal: decimal_encoder,
- Enum: lambda o: o.value,
- frozenset: list,
- deque: list,
- GeneratorType: list,
- IPv4Address: str,
- IPv4Interface: str,
- IPv4Network: str,
- IPv6Address: str,
- IPv6Interface: str,
- IPv6Network: str,
- NameEmail: str,
- Path: str,
- Pattern: lambda o: o.pattern,
- SecretBytes: str,
- SecretStr: str,
- set: list,
- UUID: str,
- Url: str,
- AnyUrl: str,
-}
-
-
-def generate_encoders_by_class_tuples(
- type_encoder_map: dict[Any, Callable[[Any], Any]],
-) -> dict[Callable[[Any], Any], tuple[Any, ...]]:
- encoders_by_class_tuples: dict[Callable[[Any], Any], tuple[Any, ...]] = defaultdict(
- tuple
- )
- for type_, encoder in type_encoder_map.items():
- encoders_by_class_tuples[encoder] += (type_,)
- return encoders_by_class_tuples
-
-
-encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE)
-
-
-def jsonable_encoder(
- obj: Annotated[
- Any,
- Doc(
- """
- The input object to convert to JSON.
- """
- ),
- ],
- include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Pydantic's `include` parameter, passed to Pydantic models to set the
- fields to include.
- """
- ),
- ] = None,
- exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Pydantic's `exclude` parameter, passed to Pydantic models to set the
- fields to exclude.
- """
- ),
- ] = None,
- by_alias: Annotated[
- bool,
- Doc(
- """
- Pydantic's `by_alias` parameter, passed to Pydantic models to define if
- the output should use the alias names (when provided) or the Python
- attribute names. In an API, if you set an alias, it's probably because you
- want to use it in the result, so you probably want to leave this set to
- `True`.
- """
- ),
- ] = True,
- exclude_unset: Annotated[
- bool,
- Doc(
- """
- Pydantic's `exclude_unset` parameter, passed to Pydantic models to define
- if it should exclude from the output the fields that were not explicitly
- set (and that only had their default values).
- """
- ),
- ] = False,
- exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Pydantic's `exclude_defaults` parameter, passed to Pydantic models to define
- if it should exclude from the output the fields that had the same default
- value, even when they were explicitly set.
- """
- ),
- ] = False,
- exclude_none: Annotated[
- bool,
- Doc(
- """
- Pydantic's `exclude_none` parameter, passed to Pydantic models to define
- if it should exclude from the output any fields that have a `None` value.
- """
- ),
- ] = False,
- custom_encoder: Annotated[
- Optional[dict[Any, Callable[[Any], Any]]],
- Doc(
- """
- Pydantic's `custom_encoder` parameter, passed to Pydantic models to define
- a custom encoder.
- """
- ),
- ] = None,
- sqlalchemy_safe: Annotated[
- bool,
- Doc(
- """
- Exclude from the output any fields that start with the name `_sa`.
-
- This is mainly a hack for compatibility with SQLAlchemy objects, they
- store internal SQLAlchemy-specific state in attributes named with `_sa`,
- and those objects can't (and shouldn't be) serialized to JSON.
- """
- ),
- ] = True,
-) -> Any:
- """
- Convert any object to something that can be encoded in JSON.
-
- This is used internally by FastAPI to make sure anything you return can be
- encoded as JSON before it is sent to the client.
-
- You can also use it yourself, for example to convert objects before saving them
- in a database that supports only JSON.
-
- Read more about it in the
- [FastAPI docs for JSON Compatible Encoder](https://fastapi.tiangolo.com/tutorial/encoder/).
- """
- custom_encoder = custom_encoder or {}
- if custom_encoder:
- if type(obj) in custom_encoder:
- return custom_encoder[type(obj)](obj)
- else:
- for encoder_type, encoder_instance in custom_encoder.items():
- if isinstance(obj, encoder_type):
- return encoder_instance(obj)
- if include is not None and not isinstance(include, (set, dict)):
- include = set(include)
- if exclude is not None and not isinstance(exclude, (set, dict)):
- exclude = set(exclude)
- if isinstance(obj, BaseModel):
- obj_dict = obj.model_dump(
- mode="json",
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_none=exclude_none,
- exclude_defaults=exclude_defaults,
- )
- return jsonable_encoder(
- obj_dict,
- exclude_none=exclude_none,
- exclude_defaults=exclude_defaults,
- sqlalchemy_safe=sqlalchemy_safe,
- )
- if dataclasses.is_dataclass(obj):
- assert not isinstance(obj, type)
- obj_dict = dataclasses.asdict(obj)
- return jsonable_encoder(
- obj_dict,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- custom_encoder=custom_encoder,
- sqlalchemy_safe=sqlalchemy_safe,
- )
- if isinstance(obj, Enum):
- return obj.value
- if isinstance(obj, PurePath):
- return str(obj)
- if isinstance(obj, (str, int, float, type(None))):
- return obj
- if isinstance(obj, PydanticUndefinedType):
- return None
- if isinstance(obj, dict):
- encoded_dict = {}
- allowed_keys = set(obj.keys())
- if include is not None:
- allowed_keys &= set(include)
- if exclude is not None:
- allowed_keys -= set(exclude)
- for key, value in obj.items():
- if (
- (
- not sqlalchemy_safe
- or (not isinstance(key, str))
- or (not key.startswith("_sa"))
- )
- and (value is not None or not exclude_none)
- and key in allowed_keys
- ):
- encoded_key = jsonable_encoder(
- key,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_none=exclude_none,
- custom_encoder=custom_encoder,
- sqlalchemy_safe=sqlalchemy_safe,
- )
- encoded_value = jsonable_encoder(
- value,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_none=exclude_none,
- custom_encoder=custom_encoder,
- sqlalchemy_safe=sqlalchemy_safe,
- )
- encoded_dict[encoded_key] = encoded_value
- return encoded_dict
- if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
- encoded_list = []
- for item in obj:
- encoded_list.append(
- jsonable_encoder(
- item,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- custom_encoder=custom_encoder,
- sqlalchemy_safe=sqlalchemy_safe,
- )
- )
- return encoded_list
-
- if type(obj) in ENCODERS_BY_TYPE:
- return ENCODERS_BY_TYPE[type(obj)](obj)
- for encoder, classes_tuple in encoders_by_class_tuples.items():
- if isinstance(obj, classes_tuple):
- return encoder(obj)
- if is_pydantic_v1_model_instance(obj):
- raise PydanticV1NotSupportedError(
- "pydantic.v1 models are no longer supported by FastAPI."
- f" Please update the model {obj!r}."
- )
- try:
- data = dict(obj)
- except Exception as e:
- errors: list[Exception] = []
- errors.append(e)
- try:
- data = vars(obj)
- except Exception as e:
- errors.append(e)
- raise ValueError(errors) from e
- return jsonable_encoder(
- data,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- custom_encoder=custom_encoder,
- sqlalchemy_safe=sqlalchemy_safe,
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/exception_handlers.py b/backend/.venv/lib/python3.12/site-packages/fastapi/exception_handlers.py
deleted file mode 100644
index 475dd7b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/exception_handlers.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from fastapi.encoders import jsonable_encoder
-from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
-from fastapi.utils import is_body_allowed_for_status_code
-from fastapi.websockets import WebSocket
-from starlette.exceptions import HTTPException
-from starlette.requests import Request
-from starlette.responses import JSONResponse, Response
-from starlette.status import WS_1008_POLICY_VIOLATION
-
-
-async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
- headers = getattr(exc, "headers", None)
- if not is_body_allowed_for_status_code(exc.status_code):
- return Response(status_code=exc.status_code, headers=headers)
- return JSONResponse(
- {"detail": exc.detail}, status_code=exc.status_code, headers=headers
- )
-
-
-async def request_validation_exception_handler(
- request: Request, exc: RequestValidationError
-) -> JSONResponse:
- return JSONResponse(
- status_code=422,
- content={"detail": jsonable_encoder(exc.errors())},
- )
-
-
-async def websocket_request_validation_exception_handler(
- websocket: WebSocket, exc: WebSocketRequestValidationError
-) -> None:
- await websocket.close(
- code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors())
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/exceptions.py b/backend/.venv/lib/python3.12/site-packages/fastapi/exceptions.py
deleted file mode 100644
index 1a3abd8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/exceptions.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from collections.abc import Sequence
-from typing import Annotated, Any, Optional, TypedDict, Union
-
-from annotated_doc import Doc
-from pydantic import BaseModel, create_model
-from starlette.exceptions import HTTPException as StarletteHTTPException
-from starlette.exceptions import WebSocketException as StarletteWebSocketException
-
-
-class EndpointContext(TypedDict, total=False):
- function: str
- path: str
- file: str
- line: int
-
-
-class HTTPException(StarletteHTTPException):
- """
- An HTTP exception you can raise in your own code to show errors to the client.
-
- This is for client errors, invalid authentication, invalid data, etc. Not for server
- errors in your code.
-
- Read more about it in the
- [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/).
-
- ## Example
-
- ```python
- from fastapi import FastAPI, HTTPException
-
- app = FastAPI()
-
- items = {"foo": "The Foo Wrestlers"}
-
-
- @app.get("/items/{item_id}")
- async def read_item(item_id: str):
- if item_id not in items:
- raise HTTPException(status_code=404, detail="Item not found")
- return {"item": items[item_id]}
- ```
- """
-
- def __init__(
- self,
- status_code: Annotated[
- int,
- Doc(
- """
- HTTP status code to send to the client.
- """
- ),
- ],
- detail: Annotated[
- Any,
- Doc(
- """
- Any data to be sent to the client in the `detail` key of the JSON
- response.
- """
- ),
- ] = None,
- headers: Annotated[
- Optional[dict[str, str]],
- Doc(
- """
- Any headers to send to the client in the response.
- """
- ),
- ] = None,
- ) -> None:
- super().__init__(status_code=status_code, detail=detail, headers=headers)
-
-
-class WebSocketException(StarletteWebSocketException):
- """
- A WebSocket exception you can raise in your own code to show errors to the client.
-
- This is for client errors, invalid authentication, invalid data, etc. Not for server
- errors in your code.
-
- Read more about it in the
- [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import (
- Cookie,
- FastAPI,
- WebSocket,
- WebSocketException,
- status,
- )
-
- app = FastAPI()
-
- @app.websocket("/items/{item_id}/ws")
- async def websocket_endpoint(
- *,
- websocket: WebSocket,
- session: Annotated[str | None, Cookie()] = None,
- item_id: str,
- ):
- if session is None:
- raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
- await websocket.accept()
- while True:
- data = await websocket.receive_text()
- await websocket.send_text(f"Session cookie is: {session}")
- await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")
- ```
- """
-
- def __init__(
- self,
- code: Annotated[
- int,
- Doc(
- """
- A closing code from the
- [valid codes defined in the specification](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1).
- """
- ),
- ],
- reason: Annotated[
- Union[str, None],
- Doc(
- """
- The reason to close the WebSocket connection.
-
- It is UTF-8-encoded data. The interpretation of the reason is up to the
- application, it is not specified by the WebSocket specification.
-
- It could contain text that could be human-readable or interpretable
- by the client code, etc.
- """
- ),
- ] = None,
- ) -> None:
- super().__init__(code=code, reason=reason)
-
-
-RequestErrorModel: type[BaseModel] = create_model("Request")
-WebSocketErrorModel: type[BaseModel] = create_model("WebSocket")
-
-
-class FastAPIError(RuntimeError):
- """
- A generic, FastAPI-specific error.
- """
-
-
-class DependencyScopeError(FastAPIError):
- """
- A dependency declared that it depends on another dependency with an invalid
- (narrower) scope.
- """
-
-
-class ValidationException(Exception):
- def __init__(
- self,
- errors: Sequence[Any],
- *,
- endpoint_ctx: Optional[EndpointContext] = None,
- ) -> None:
- self._errors = errors
- self.endpoint_ctx = endpoint_ctx
-
- ctx = endpoint_ctx or {}
- self.endpoint_function = ctx.get("function")
- self.endpoint_path = ctx.get("path")
- self.endpoint_file = ctx.get("file")
- self.endpoint_line = ctx.get("line")
-
- def errors(self) -> Sequence[Any]:
- return self._errors
-
- def _format_endpoint_context(self) -> str:
- if not (self.endpoint_file and self.endpoint_line and self.endpoint_function):
- if self.endpoint_path:
- return f"\n Endpoint: {self.endpoint_path}"
- return ""
-
- context = f'\n File "{self.endpoint_file}", line {self.endpoint_line}, in {self.endpoint_function}'
- if self.endpoint_path:
- context += f"\n {self.endpoint_path}"
- return context
-
- def __str__(self) -> str:
- message = f"{len(self._errors)} validation error{'s' if len(self._errors) != 1 else ''}:\n"
- for err in self._errors:
- message += f" {err}\n"
- message += self._format_endpoint_context()
- return message.rstrip()
-
-
-class RequestValidationError(ValidationException):
- def __init__(
- self,
- errors: Sequence[Any],
- *,
- body: Any = None,
- endpoint_ctx: Optional[EndpointContext] = None,
- ) -> None:
- super().__init__(errors, endpoint_ctx=endpoint_ctx)
- self.body = body
-
-
-class WebSocketRequestValidationError(ValidationException):
- def __init__(
- self,
- errors: Sequence[Any],
- *,
- endpoint_ctx: Optional[EndpointContext] = None,
- ) -> None:
- super().__init__(errors, endpoint_ctx=endpoint_ctx)
-
-
-class ResponseValidationError(ValidationException):
- def __init__(
- self,
- errors: Sequence[Any],
- *,
- body: Any = None,
- endpoint_ctx: Optional[EndpointContext] = None,
- ) -> None:
- super().__init__(errors, endpoint_ctx=endpoint_ctx)
- self.body = body
-
-
-class PydanticV1NotSupportedError(FastAPIError):
- """
- A pydantic.v1 model is used, which is no longer supported.
- """
-
-
-class FastAPIDeprecationWarning(UserWarning):
- """
- A custom deprecation warning as DeprecationWarning is ignored
- Ref: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
- """
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/logger.py b/backend/.venv/lib/python3.12/site-packages/fastapi/logger.py
deleted file mode 100644
index 5b2c4ad..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/logger.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import logging
-
-logger = logging.getLogger("fastapi")
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__init__.py
deleted file mode 100644
index 620296d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.middleware import Middleware as Middleware
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 4c6a3f8..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc
deleted file mode 100644
index 5cc26d9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc
deleted file mode 100644
index 8274842..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc
deleted file mode 100644
index 9d6dc18..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc
deleted file mode 100644
index ccd094a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc
deleted file mode 100644
index 750b999..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc
deleted file mode 100644
index 11e828e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py
deleted file mode 100644
index 4ce3f5a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from contextlib import AsyncExitStack
-
-from starlette.types import ASGIApp, Receive, Scope, Send
-
-
-# Used mainly to close files after the request is done, dependencies are closed
-# in their own AsyncExitStack
-class AsyncExitStackMiddleware:
- def __init__(
- self, app: ASGIApp, context_name: str = "fastapi_middleware_astack"
- ) -> None:
- self.app = app
- self.context_name = context_name
-
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
- async with AsyncExitStack() as stack:
- scope[self.context_name] = stack
- await self.app(scope, receive, send)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/cors.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/cors.py
deleted file mode 100644
index 8dfaad0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/cors.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.middleware.cors import CORSMiddleware as CORSMiddleware # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/gzip.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/gzip.py
deleted file mode 100644
index bbeb2cc..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/gzip.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.middleware.gzip import GZipMiddleware as GZipMiddleware # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/httpsredirect.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/httpsredirect.py
deleted file mode 100644
index b7a3d8e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/httpsredirect.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from starlette.middleware.httpsredirect import ( # noqa
- HTTPSRedirectMiddleware as HTTPSRedirectMiddleware,
-)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/trustedhost.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/trustedhost.py
deleted file mode 100644
index 08d7e03..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/trustedhost.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from starlette.middleware.trustedhost import ( # noqa
- TrustedHostMiddleware as TrustedHostMiddleware,
-)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/wsgi.py b/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/wsgi.py
deleted file mode 100644
index c4c6a79..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/middleware/wsgi.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.middleware.wsgi import WSGIMiddleware as WSGIMiddleware # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index bba387a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc
deleted file mode 100644
index e278ef3..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc
deleted file mode 100644
index 471c96c..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc
deleted file mode 100644
index 58855d9..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc
deleted file mode 100644
index 904ff9a..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/constants.py b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/constants.py
deleted file mode 100644
index d724ee3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/constants.py
+++ /dev/null
@@ -1,3 +0,0 @@
-METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"}
-REF_PREFIX = "#/components/schemas/"
-REF_TEMPLATE = "#/components/schemas/{model}"
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/docs.py b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/docs.py
deleted file mode 100644
index 82380f8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/docs.py
+++ /dev/null
@@ -1,344 +0,0 @@
-import json
-from typing import Annotated, Any, Optional
-
-from annotated_doc import Doc
-from fastapi.encoders import jsonable_encoder
-from starlette.responses import HTMLResponse
-
-swagger_ui_default_parameters: Annotated[
- dict[str, Any],
- Doc(
- """
- Default configurations for Swagger UI.
-
- You can use it as a template to add any other configurations needed.
- """
- ),
-] = {
- "dom_id": "#swagger-ui",
- "layout": "BaseLayout",
- "deepLinking": True,
- "showExtensions": True,
- "showCommonExtensions": True,
-}
-
-
-def get_swagger_ui_html(
- *,
- openapi_url: Annotated[
- str,
- Doc(
- """
- The OpenAPI URL that Swagger UI should load and use.
-
- This is normally done automatically by FastAPI using the default URL
- `/openapi.json`.
- """
- ),
- ],
- title: Annotated[
- str,
- Doc(
- """
- The HTML `` content, normally shown in the browser tab.
- """
- ),
- ],
- swagger_js_url: Annotated[
- str,
- Doc(
- """
- The URL to use to load the Swagger UI JavaScript.
-
- It is normally set to a CDN URL.
- """
- ),
- ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
- swagger_css_url: Annotated[
- str,
- Doc(
- """
- The URL to use to load the Swagger UI CSS.
-
- It is normally set to a CDN URL.
- """
- ),
- ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
- swagger_favicon_url: Annotated[
- str,
- Doc(
- """
- The URL of the favicon to use. It is normally shown in the browser tab.
- """
- ),
- ] = "https://fastapi.tiangolo.com/img/favicon.png",
- oauth2_redirect_url: Annotated[
- Optional[str],
- Doc(
- """
- The OAuth2 redirect URL, it is normally automatically handled by FastAPI.
- """
- ),
- ] = None,
- init_oauth: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- A dictionary with Swagger UI OAuth2 initialization configurations.
- """
- ),
- ] = None,
- swagger_ui_parameters: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Configuration parameters for Swagger UI.
-
- It defaults to [swagger_ui_default_parameters][fastapi.openapi.docs.swagger_ui_default_parameters].
- """
- ),
- ] = None,
-) -> HTMLResponse:
- """
- Generate and return the HTML that loads Swagger UI for the interactive
- API docs (normally served at `/docs`).
-
- You would only call this function yourself if you needed to override some parts,
- for example the URLs to use to load Swagger UI's JavaScript and CSS.
-
- Read more about it in the
- [FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/)
- and the [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/).
- """
- current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
- if swagger_ui_parameters:
- current_swagger_ui_parameters.update(swagger_ui_parameters)
-
- html = f"""
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
- """
- return HTMLResponse(html)
-
-
-def get_redoc_html(
- *,
- openapi_url: Annotated[
- str,
- Doc(
- """
- The OpenAPI URL that ReDoc should load and use.
-
- This is normally done automatically by FastAPI using the default URL
- `/openapi.json`.
- """
- ),
- ],
- title: Annotated[
- str,
- Doc(
- """
- The HTML `` content, normally shown in the browser tab.
- """
- ),
- ],
- redoc_js_url: Annotated[
- str,
- Doc(
- """
- The URL to use to load the ReDoc JavaScript.
-
- It is normally set to a CDN URL.
- """
- ),
- ] = "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js",
- redoc_favicon_url: Annotated[
- str,
- Doc(
- """
- The URL of the favicon to use. It is normally shown in the browser tab.
- """
- ),
- ] = "https://fastapi.tiangolo.com/img/favicon.png",
- with_google_fonts: Annotated[
- bool,
- Doc(
- """
- Load and use Google Fonts.
- """
- ),
- ] = True,
-) -> HTMLResponse:
- """
- Generate and return the HTML response that loads ReDoc for the alternative
- API docs (normally served at `/redoc`).
-
- You would only call this function yourself if you needed to override some parts,
- for example the URLs to use to load ReDoc's JavaScript and CSS.
-
- Read more about it in the
- [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/).
- """
- html = f"""
-
-
-
- {title}
-
-
-
- """
- if with_google_fonts:
- html += """
-
- """
- html += f"""
-
-
-
-
-
-
-
-
-
-
- """
- return HTMLResponse(html)
-
-
-def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
- """
- Generate the HTML response with the OAuth2 redirection for Swagger UI.
-
- You normally don't need to use or change this.
- """
- # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
- html = """
-
-
-
- Swagger UI: OAuth2 Redirect
-
-
-
-
-
- """
- return HTMLResponse(content=html)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/models.py b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/models.py
deleted file mode 100644
index ac6a6d5..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/models.py
+++ /dev/null
@@ -1,438 +0,0 @@
-from collections.abc import Iterable, Mapping
-from enum import Enum
-from typing import Annotated, Any, Callable, Optional, Union
-
-from fastapi._compat import with_info_plain_validator_function
-from fastapi.logger import logger
-from pydantic import (
- AnyUrl,
- BaseModel,
- Field,
- GetJsonSchemaHandler,
-)
-from typing_extensions import Literal, TypedDict
-from typing_extensions import deprecated as typing_deprecated
-
-try:
- import email_validator
-
- assert email_validator # make autoflake ignore the unused import
- from pydantic import EmailStr
-except ImportError: # pragma: no cover
-
- class EmailStr(str): # type: ignore
- @classmethod
- def __get_validators__(cls) -> Iterable[Callable[..., Any]]:
- yield cls.validate
-
- @classmethod
- def validate(cls, v: Any) -> str:
- logger.warning(
- "email-validator not installed, email fields will be treated as str.\n"
- "To install, run: pip install email-validator"
- )
- return str(v)
-
- @classmethod
- def _validate(cls, __input_value: Any, _: Any) -> str:
- logger.warning(
- "email-validator not installed, email fields will be treated as str.\n"
- "To install, run: pip install email-validator"
- )
- return str(__input_value)
-
- @classmethod
- def __get_pydantic_json_schema__(
- cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
- ) -> dict[str, Any]:
- return {"type": "string", "format": "email"}
-
- @classmethod
- def __get_pydantic_core_schema__(
- cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]]
- ) -> Mapping[str, Any]:
- return with_info_plain_validator_function(cls._validate)
-
-
-class BaseModelWithConfig(BaseModel):
- model_config = {"extra": "allow"}
-
-
-class Contact(BaseModelWithConfig):
- name: Optional[str] = None
- url: Optional[AnyUrl] = None
- email: Optional[EmailStr] = None
-
-
-class License(BaseModelWithConfig):
- name: str
- identifier: Optional[str] = None
- url: Optional[AnyUrl] = None
-
-
-class Info(BaseModelWithConfig):
- title: str
- summary: Optional[str] = None
- description: Optional[str] = None
- termsOfService: Optional[str] = None
- contact: Optional[Contact] = None
- license: Optional[License] = None
- version: str
-
-
-class ServerVariable(BaseModelWithConfig):
- enum: Annotated[Optional[list[str]], Field(min_length=1)] = None
- default: str
- description: Optional[str] = None
-
-
-class Server(BaseModelWithConfig):
- url: Union[AnyUrl, str]
- description: Optional[str] = None
- variables: Optional[dict[str, ServerVariable]] = None
-
-
-class Reference(BaseModel):
- ref: str = Field(alias="$ref")
-
-
-class Discriminator(BaseModel):
- propertyName: str
- mapping: Optional[dict[str, str]] = None
-
-
-class XML(BaseModelWithConfig):
- name: Optional[str] = None
- namespace: Optional[str] = None
- prefix: Optional[str] = None
- attribute: Optional[bool] = None
- wrapped: Optional[bool] = None
-
-
-class ExternalDocumentation(BaseModelWithConfig):
- description: Optional[str] = None
- url: AnyUrl
-
-
-# Ref JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation#name-type
-SchemaType = Literal[
- "array", "boolean", "integer", "null", "number", "object", "string"
-]
-
-
-class Schema(BaseModelWithConfig):
- # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu
- # Core Vocabulary
- schema_: Optional[str] = Field(default=None, alias="$schema")
- vocabulary: Optional[str] = Field(default=None, alias="$vocabulary")
- id: Optional[str] = Field(default=None, alias="$id")
- anchor: Optional[str] = Field(default=None, alias="$anchor")
- dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor")
- ref: Optional[str] = Field(default=None, alias="$ref")
- dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef")
- defs: Optional[dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs")
- comment: Optional[str] = Field(default=None, alias="$comment")
- # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s
- # A Vocabulary for Applying Subschemas
- allOf: Optional[list["SchemaOrBool"]] = None
- anyOf: Optional[list["SchemaOrBool"]] = None
- oneOf: Optional[list["SchemaOrBool"]] = None
- not_: Optional["SchemaOrBool"] = Field(default=None, alias="not")
- if_: Optional["SchemaOrBool"] = Field(default=None, alias="if")
- then: Optional["SchemaOrBool"] = None
- else_: Optional["SchemaOrBool"] = Field(default=None, alias="else")
- dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None
- prefixItems: Optional[list["SchemaOrBool"]] = None
- # TODO: uncomment and remove below when deprecating Pydantic v1
- # It generates a list of schemas for tuples, before prefixItems was available
- # items: Optional["SchemaOrBool"] = None
- items: Optional[Union["SchemaOrBool", list["SchemaOrBool"]]] = None
- contains: Optional["SchemaOrBool"] = None
- properties: Optional[dict[str, "SchemaOrBool"]] = None
- patternProperties: Optional[dict[str, "SchemaOrBool"]] = None
- additionalProperties: Optional["SchemaOrBool"] = None
- propertyNames: Optional["SchemaOrBool"] = None
- unevaluatedItems: Optional["SchemaOrBool"] = None
- unevaluatedProperties: Optional["SchemaOrBool"] = None
- # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural
- # A Vocabulary for Structural Validation
- type: Optional[Union[SchemaType, list[SchemaType]]] = None
- enum: Optional[list[Any]] = None
- const: Optional[Any] = None
- multipleOf: Optional[float] = Field(default=None, gt=0)
- maximum: Optional[float] = None
- exclusiveMaximum: Optional[float] = None
- minimum: Optional[float] = None
- exclusiveMinimum: Optional[float] = None
- maxLength: Optional[int] = Field(default=None, ge=0)
- minLength: Optional[int] = Field(default=None, ge=0)
- pattern: Optional[str] = None
- maxItems: Optional[int] = Field(default=None, ge=0)
- minItems: Optional[int] = Field(default=None, ge=0)
- uniqueItems: Optional[bool] = None
- maxContains: Optional[int] = Field(default=None, ge=0)
- minContains: Optional[int] = Field(default=None, ge=0)
- maxProperties: Optional[int] = Field(default=None, ge=0)
- minProperties: Optional[int] = Field(default=None, ge=0)
- required: Optional[list[str]] = None
- dependentRequired: Optional[dict[str, set[str]]] = None
- # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c
- # Vocabularies for Semantic Content With "format"
- format: Optional[str] = None
- # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten
- # A Vocabulary for the Contents of String-Encoded Data
- contentEncoding: Optional[str] = None
- contentMediaType: Optional[str] = None
- contentSchema: Optional["SchemaOrBool"] = None
- # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
- # A Vocabulary for Basic Meta-Data Annotations
- title: Optional[str] = None
- description: Optional[str] = None
- default: Optional[Any] = None
- deprecated: Optional[bool] = None
- readOnly: Optional[bool] = None
- writeOnly: Optional[bool] = None
- examples: Optional[list[Any]] = None
- # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object
- # Schema Object
- discriminator: Optional[Discriminator] = None
- xml: Optional[XML] = None
- externalDocs: Optional[ExternalDocumentation] = None
- example: Annotated[
- Optional[Any],
- typing_deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = None
-
-
-# Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents
-# A JSON Schema MUST be an object or a boolean.
-SchemaOrBool = Union[Schema, bool]
-
-
-class Example(TypedDict, total=False):
- summary: Optional[str]
- description: Optional[str]
- value: Optional[Any]
- externalValue: Optional[AnyUrl]
-
- __pydantic_config__ = {"extra": "allow"} # type: ignore[misc]
-
-
-class ParameterInType(Enum):
- query = "query"
- header = "header"
- path = "path"
- cookie = "cookie"
-
-
-class Encoding(BaseModelWithConfig):
- contentType: Optional[str] = None
- headers: Optional[dict[str, Union["Header", Reference]]] = None
- style: Optional[str] = None
- explode: Optional[bool] = None
- allowReserved: Optional[bool] = None
-
-
-class MediaType(BaseModelWithConfig):
- schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema")
- example: Optional[Any] = None
- examples: Optional[dict[str, Union[Example, Reference]]] = None
- encoding: Optional[dict[str, Encoding]] = None
-
-
-class ParameterBase(BaseModelWithConfig):
- description: Optional[str] = None
- required: Optional[bool] = None
- deprecated: Optional[bool] = None
- # Serialization rules for simple scenarios
- style: Optional[str] = None
- explode: Optional[bool] = None
- allowReserved: Optional[bool] = None
- schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema")
- example: Optional[Any] = None
- examples: Optional[dict[str, Union[Example, Reference]]] = None
- # Serialization rules for more complex scenarios
- content: Optional[dict[str, MediaType]] = None
-
-
-class Parameter(ParameterBase):
- name: str
- in_: ParameterInType = Field(alias="in")
-
-
-class Header(ParameterBase):
- pass
-
-
-class RequestBody(BaseModelWithConfig):
- description: Optional[str] = None
- content: dict[str, MediaType]
- required: Optional[bool] = None
-
-
-class Link(BaseModelWithConfig):
- operationRef: Optional[str] = None
- operationId: Optional[str] = None
- parameters: Optional[dict[str, Union[Any, str]]] = None
- requestBody: Optional[Union[Any, str]] = None
- description: Optional[str] = None
- server: Optional[Server] = None
-
-
-class Response(BaseModelWithConfig):
- description: str
- headers: Optional[dict[str, Union[Header, Reference]]] = None
- content: Optional[dict[str, MediaType]] = None
- links: Optional[dict[str, Union[Link, Reference]]] = None
-
-
-class Operation(BaseModelWithConfig):
- tags: Optional[list[str]] = None
- summary: Optional[str] = None
- description: Optional[str] = None
- externalDocs: Optional[ExternalDocumentation] = None
- operationId: Optional[str] = None
- parameters: Optional[list[Union[Parameter, Reference]]] = None
- requestBody: Optional[Union[RequestBody, Reference]] = None
- # Using Any for Specification Extensions
- responses: Optional[dict[str, Union[Response, Any]]] = None
- callbacks: Optional[dict[str, Union[dict[str, "PathItem"], Reference]]] = None
- deprecated: Optional[bool] = None
- security: Optional[list[dict[str, list[str]]]] = None
- servers: Optional[list[Server]] = None
-
-
-class PathItem(BaseModelWithConfig):
- ref: Optional[str] = Field(default=None, alias="$ref")
- summary: Optional[str] = None
- description: Optional[str] = None
- get: Optional[Operation] = None
- put: Optional[Operation] = None
- post: Optional[Operation] = None
- delete: Optional[Operation] = None
- options: Optional[Operation] = None
- head: Optional[Operation] = None
- patch: Optional[Operation] = None
- trace: Optional[Operation] = None
- servers: Optional[list[Server]] = None
- parameters: Optional[list[Union[Parameter, Reference]]] = None
-
-
-class SecuritySchemeType(Enum):
- apiKey = "apiKey"
- http = "http"
- oauth2 = "oauth2"
- openIdConnect = "openIdConnect"
-
-
-class SecurityBase(BaseModelWithConfig):
- type_: SecuritySchemeType = Field(alias="type")
- description: Optional[str] = None
-
-
-class APIKeyIn(Enum):
- query = "query"
- header = "header"
- cookie = "cookie"
-
-
-class APIKey(SecurityBase):
- type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type")
- in_: APIKeyIn = Field(alias="in")
- name: str
-
-
-class HTTPBase(SecurityBase):
- type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type")
- scheme: str
-
-
-class HTTPBearer(HTTPBase):
- scheme: Literal["bearer"] = "bearer"
- bearerFormat: Optional[str] = None
-
-
-class OAuthFlow(BaseModelWithConfig):
- refreshUrl: Optional[str] = None
- scopes: dict[str, str] = {}
-
-
-class OAuthFlowImplicit(OAuthFlow):
- authorizationUrl: str
-
-
-class OAuthFlowPassword(OAuthFlow):
- tokenUrl: str
-
-
-class OAuthFlowClientCredentials(OAuthFlow):
- tokenUrl: str
-
-
-class OAuthFlowAuthorizationCode(OAuthFlow):
- authorizationUrl: str
- tokenUrl: str
-
-
-class OAuthFlows(BaseModelWithConfig):
- implicit: Optional[OAuthFlowImplicit] = None
- password: Optional[OAuthFlowPassword] = None
- clientCredentials: Optional[OAuthFlowClientCredentials] = None
- authorizationCode: Optional[OAuthFlowAuthorizationCode] = None
-
-
-class OAuth2(SecurityBase):
- type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type")
- flows: OAuthFlows
-
-
-class OpenIdConnect(SecurityBase):
- type_: SecuritySchemeType = Field(
- default=SecuritySchemeType.openIdConnect, alias="type"
- )
- openIdConnectUrl: str
-
-
-SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer]
-
-
-class Components(BaseModelWithConfig):
- schemas: Optional[dict[str, Union[Schema, Reference]]] = None
- responses: Optional[dict[str, Union[Response, Reference]]] = None
- parameters: Optional[dict[str, Union[Parameter, Reference]]] = None
- examples: Optional[dict[str, Union[Example, Reference]]] = None
- requestBodies: Optional[dict[str, Union[RequestBody, Reference]]] = None
- headers: Optional[dict[str, Union[Header, Reference]]] = None
- securitySchemes: Optional[dict[str, Union[SecurityScheme, Reference]]] = None
- links: Optional[dict[str, Union[Link, Reference]]] = None
- # Using Any for Specification Extensions
- callbacks: Optional[dict[str, Union[dict[str, PathItem], Reference, Any]]] = None
- pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None
-
-
-class Tag(BaseModelWithConfig):
- name: str
- description: Optional[str] = None
- externalDocs: Optional[ExternalDocumentation] = None
-
-
-class OpenAPI(BaseModelWithConfig):
- openapi: str
- info: Info
- jsonSchemaDialect: Optional[str] = None
- servers: Optional[list[Server]] = None
- # Using Any for Specification Extensions
- paths: Optional[dict[str, Union[PathItem, Any]]] = None
- webhooks: Optional[dict[str, Union[PathItem, Reference]]] = None
- components: Optional[Components] = None
- security: Optional[list[dict[str, list[str]]]] = None
- tags: Optional[list[Tag]] = None
- externalDocs: Optional[ExternalDocumentation] = None
-
-
-Schema.model_rebuild()
-Operation.model_rebuild()
-Encoding.model_rebuild()
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/utils.py b/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/utils.py
deleted file mode 100644
index 75ff261..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/openapi/utils.py
+++ /dev/null
@@ -1,567 +0,0 @@
-import http.client
-import inspect
-import warnings
-from collections.abc import Sequence
-from typing import Any, Optional, Union, cast
-
-from fastapi import routing
-from fastapi._compat import (
- ModelField,
- Undefined,
- get_compat_model_name_map,
- get_definitions,
- get_schema_from_model_field,
- lenient_issubclass,
-)
-from fastapi.datastructures import DefaultPlaceholder
-from fastapi.dependencies.models import Dependant
-from fastapi.dependencies.utils import (
- _get_flat_fields_from_params,
- get_flat_dependant,
- get_flat_params,
- get_validation_alias,
-)
-from fastapi.encoders import jsonable_encoder
-from fastapi.exceptions import FastAPIDeprecationWarning
-from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
-from fastapi.openapi.models import OpenAPI
-from fastapi.params import Body, ParamTypes
-from fastapi.responses import Response
-from fastapi.types import ModelNameMap
-from fastapi.utils import (
- deep_dict_update,
- generate_operation_id_for_path,
- is_body_allowed_for_status_code,
-)
-from pydantic import BaseModel
-from starlette.responses import JSONResponse
-from starlette.routing import BaseRoute
-from typing_extensions import Literal
-
-validation_error_definition = {
- "title": "ValidationError",
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- "required": ["loc", "msg", "type"],
-}
-
-validation_error_response_definition = {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": REF_PREFIX + "ValidationError"},
- }
- },
-}
-
-status_code_ranges: dict[str, str] = {
- "1XX": "Information",
- "2XX": "Success",
- "3XX": "Redirection",
- "4XX": "Client Error",
- "5XX": "Server Error",
- "DEFAULT": "Default Response",
-}
-
-
-def get_openapi_security_definitions(
- flat_dependant: Dependant,
-) -> tuple[dict[str, Any], list[dict[str, Any]]]:
- security_definitions = {}
- # Use a dict to merge scopes for same security scheme
- operation_security_dict: dict[str, list[str]] = {}
- for security_dependency in flat_dependant._security_dependencies:
- security_definition = jsonable_encoder(
- security_dependency._security_scheme.model,
- by_alias=True,
- exclude_none=True,
- )
- security_name = security_dependency._security_scheme.scheme_name
- security_definitions[security_name] = security_definition
- # Merge scopes for the same security scheme
- if security_name not in operation_security_dict:
- operation_security_dict[security_name] = []
- for scope in security_dependency.oauth_scopes or []:
- if scope not in operation_security_dict[security_name]:
- operation_security_dict[security_name].append(scope)
- operation_security = [
- {name: scopes} for name, scopes in operation_security_dict.items()
- ]
- return security_definitions, operation_security
-
-
-def _get_openapi_operation_parameters(
- *,
- dependant: Dependant,
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
- ],
- separate_input_output_schemas: bool = True,
-) -> list[dict[str, Any]]:
- parameters = []
- flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
- path_params = _get_flat_fields_from_params(flat_dependant.path_params)
- query_params = _get_flat_fields_from_params(flat_dependant.query_params)
- header_params = _get_flat_fields_from_params(flat_dependant.header_params)
- cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
- parameter_groups = [
- (ParamTypes.path, path_params),
- (ParamTypes.query, query_params),
- (ParamTypes.header, header_params),
- (ParamTypes.cookie, cookie_params),
- ]
- default_convert_underscores = True
- if len(flat_dependant.header_params) == 1:
- first_field = flat_dependant.header_params[0]
- if lenient_issubclass(first_field.type_, BaseModel):
- default_convert_underscores = getattr(
- first_field.field_info, "convert_underscores", True
- )
- for param_type, param_group in parameter_groups:
- for param in param_group:
- field_info = param.field_info
- # field_info = cast(Param, field_info)
- if not getattr(field_info, "include_in_schema", True):
- continue
- param_schema = get_schema_from_model_field(
- field=param,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- name = get_validation_alias(param)
- convert_underscores = getattr(
- param.field_info,
- "convert_underscores",
- default_convert_underscores,
- )
- if (
- param_type == ParamTypes.header
- and name == param.name
- and convert_underscores
- ):
- name = param.name.replace("_", "-")
-
- parameter = {
- "name": name,
- "in": param_type.value,
- "required": param.required,
- "schema": param_schema,
- }
- if field_info.description:
- parameter["description"] = field_info.description
- openapi_examples = getattr(field_info, "openapi_examples", None)
- example = getattr(field_info, "example", None)
- if openapi_examples:
- parameter["examples"] = jsonable_encoder(openapi_examples)
- elif example != Undefined:
- parameter["example"] = jsonable_encoder(example)
- if getattr(field_info, "deprecated", None):
- parameter["deprecated"] = True
- parameters.append(parameter)
- return parameters
-
-
-def get_openapi_operation_request_body(
- *,
- body_field: Optional[ModelField],
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
- ],
- separate_input_output_schemas: bool = True,
-) -> Optional[dict[str, Any]]:
- if not body_field:
- return None
- assert isinstance(body_field, ModelField)
- body_schema = get_schema_from_model_field(
- field=body_field,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- field_info = cast(Body, body_field.field_info)
- request_media_type = field_info.media_type
- required = body_field.required
- request_body_oai: dict[str, Any] = {}
- if required:
- request_body_oai["required"] = required
- request_media_content: dict[str, Any] = {"schema": body_schema}
- if field_info.openapi_examples:
- request_media_content["examples"] = jsonable_encoder(
- field_info.openapi_examples
- )
- elif field_info.example != Undefined:
- request_media_content["example"] = jsonable_encoder(field_info.example)
- request_body_oai["content"] = {request_media_type: request_media_content}
- return request_body_oai
-
-
-def generate_operation_id(
- *, route: routing.APIRoute, method: str
-) -> str: # pragma: nocover
- warnings.warn(
- message="fastapi.openapi.utils.generate_operation_id() was deprecated, "
- "it is not used internally, and will be removed soon",
- category=FastAPIDeprecationWarning,
- stacklevel=2,
- )
- if route.operation_id:
- return route.operation_id
- path: str = route.path_format
- return generate_operation_id_for_path(name=route.name, path=path, method=method)
-
-
-def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
- if route.summary:
- return route.summary
- return route.name.replace("_", " ").title()
-
-
-def get_openapi_operation_metadata(
- *, route: routing.APIRoute, method: str, operation_ids: set[str]
-) -> dict[str, Any]:
- operation: dict[str, Any] = {}
- if route.tags:
- operation["tags"] = route.tags
- operation["summary"] = generate_operation_summary(route=route, method=method)
- if route.description:
- operation["description"] = route.description
- operation_id = route.operation_id or route.unique_id
- if operation_id in operation_ids:
- message = (
- f"Duplicate Operation ID {operation_id} for function "
- + f"{route.endpoint.__name__}"
- )
- file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
- if file_name:
- message += f" at {file_name}"
- warnings.warn(message, stacklevel=1)
- operation_ids.add(operation_id)
- operation["operationId"] = operation_id
- if route.deprecated:
- operation["deprecated"] = route.deprecated
- return operation
-
-
-def get_openapi_path(
- *,
- route: routing.APIRoute,
- operation_ids: set[str],
- model_name_map: ModelNameMap,
- field_mapping: dict[
- tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
- ],
- separate_input_output_schemas: bool = True,
-) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
- path = {}
- security_schemes: dict[str, Any] = {}
- definitions: dict[str, Any] = {}
- assert route.methods is not None, "Methods must be a list"
- if isinstance(route.response_class, DefaultPlaceholder):
- current_response_class: type[Response] = route.response_class.value
- else:
- current_response_class = route.response_class
- assert current_response_class, "A response class is needed to generate OpenAPI"
- route_response_media_type: Optional[str] = current_response_class.media_type
- if route.include_in_schema:
- for method in route.methods:
- operation = get_openapi_operation_metadata(
- route=route, method=method, operation_ids=operation_ids
- )
- parameters: list[dict[str, Any]] = []
- flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
- security_definitions, operation_security = get_openapi_security_definitions(
- flat_dependant=flat_dependant
- )
- if operation_security:
- operation.setdefault("security", []).extend(operation_security)
- if security_definitions:
- security_schemes.update(security_definitions)
- operation_parameters = _get_openapi_operation_parameters(
- dependant=route.dependant,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- parameters.extend(operation_parameters)
- if parameters:
- all_parameters = {
- (param["in"], param["name"]): param for param in parameters
- }
- required_parameters = {
- (param["in"], param["name"]): param
- for param in parameters
- if param.get("required")
- }
- # Make sure required definitions of the same parameter take precedence
- # over non-required definitions
- all_parameters.update(required_parameters)
- operation["parameters"] = list(all_parameters.values())
- if method in METHODS_WITH_BODY:
- request_body_oai = get_openapi_operation_request_body(
- body_field=route.body_field,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- if request_body_oai:
- operation["requestBody"] = request_body_oai
- if route.callbacks:
- callbacks = {}
- for callback in route.callbacks:
- if isinstance(callback, routing.APIRoute):
- (
- cb_path,
- cb_security_schemes,
- cb_definitions,
- ) = get_openapi_path(
- route=callback,
- operation_ids=operation_ids,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- callbacks[callback.name] = {callback.path: cb_path}
- operation["callbacks"] = callbacks
- if route.status_code is not None:
- status_code = str(route.status_code)
- else:
- # It would probably make more sense for all response classes to have an
- # explicit default status_code, and to extract it from them, instead of
- # doing this inspection tricks, that would probably be in the future
- # TODO: probably make status_code a default class attribute for all
- # responses in Starlette
- response_signature = inspect.signature(current_response_class.__init__)
- status_code_param = response_signature.parameters.get("status_code")
- if status_code_param is not None:
- if isinstance(status_code_param.default, int):
- status_code = str(status_code_param.default)
- operation.setdefault("responses", {}).setdefault(status_code, {})[
- "description"
- ] = route.response_description
- if route_response_media_type and is_body_allowed_for_status_code(
- route.status_code
- ):
- response_schema = {"type": "string"}
- if lenient_issubclass(current_response_class, JSONResponse):
- if route.response_field:
- response_schema = get_schema_from_model_field(
- field=route.response_field,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- else:
- response_schema = {}
- operation.setdefault("responses", {}).setdefault(
- status_code, {}
- ).setdefault("content", {}).setdefault(route_response_media_type, {})[
- "schema"
- ] = response_schema
- if route.responses:
- operation_responses = operation.setdefault("responses", {})
- for (
- additional_status_code,
- additional_response,
- ) in route.responses.items():
- process_response = additional_response.copy()
- process_response.pop("model", None)
- status_code_key = str(additional_status_code).upper()
- if status_code_key == "DEFAULT":
- status_code_key = "default"
- openapi_response = operation_responses.setdefault(
- status_code_key, {}
- )
- assert isinstance(process_response, dict), (
- "An additional response must be a dict"
- )
- field = route.response_fields.get(additional_status_code)
- additional_field_schema: Optional[dict[str, Any]] = None
- if field:
- additional_field_schema = get_schema_from_model_field(
- field=field,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- media_type = route_response_media_type or "application/json"
- additional_schema = (
- process_response.setdefault("content", {})
- .setdefault(media_type, {})
- .setdefault("schema", {})
- )
- deep_dict_update(additional_schema, additional_field_schema)
- status_text: Optional[str] = status_code_ranges.get(
- str(additional_status_code).upper()
- ) or http.client.responses.get(int(additional_status_code))
- description = (
- process_response.get("description")
- or openapi_response.get("description")
- or status_text
- or "Additional Response"
- )
- deep_dict_update(openapi_response, process_response)
- openapi_response["description"] = description
- http422 = "422"
- all_route_params = get_flat_params(route.dependant)
- if (all_route_params or route.body_field) and not any(
- status in operation["responses"]
- for status in [http422, "4XX", "default"]
- ):
- operation["responses"][http422] = {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {"$ref": REF_PREFIX + "HTTPValidationError"}
- }
- },
- }
- if "ValidationError" not in definitions:
- definitions.update(
- {
- "ValidationError": validation_error_definition,
- "HTTPValidationError": validation_error_response_definition,
- }
- )
- if route.openapi_extra:
- deep_dict_update(operation, route.openapi_extra)
- path[method.lower()] = operation
- return path, security_schemes, definitions
-
-
-def get_fields_from_routes(
- routes: Sequence[BaseRoute],
-) -> list[ModelField]:
- body_fields_from_routes: list[ModelField] = []
- responses_from_routes: list[ModelField] = []
- request_fields_from_routes: list[ModelField] = []
- callback_flat_models: list[ModelField] = []
- for route in routes:
- if getattr(route, "include_in_schema", None) and isinstance(
- route, routing.APIRoute
- ):
- if route.body_field:
- assert isinstance(route.body_field, ModelField), (
- "A request body must be a Pydantic Field"
- )
- body_fields_from_routes.append(route.body_field)
- if route.response_field:
- responses_from_routes.append(route.response_field)
- if route.response_fields:
- responses_from_routes.extend(route.response_fields.values())
- if route.callbacks:
- callback_flat_models.extend(get_fields_from_routes(route.callbacks))
- params = get_flat_params(route.dependant)
- request_fields_from_routes.extend(params)
-
- flat_models = callback_flat_models + list(
- body_fields_from_routes + responses_from_routes + request_fields_from_routes
- )
- return flat_models
-
-
-def get_openapi(
- *,
- title: str,
- version: str,
- openapi_version: str = "3.1.0",
- summary: Optional[str] = None,
- description: Optional[str] = None,
- routes: Sequence[BaseRoute],
- webhooks: Optional[Sequence[BaseRoute]] = None,
- tags: Optional[list[dict[str, Any]]] = None,
- servers: Optional[list[dict[str, Union[str, Any]]]] = None,
- terms_of_service: Optional[str] = None,
- contact: Optional[dict[str, Union[str, Any]]] = None,
- license_info: Optional[dict[str, Union[str, Any]]] = None,
- separate_input_output_schemas: bool = True,
- external_docs: Optional[dict[str, Any]] = None,
-) -> dict[str, Any]:
- info: dict[str, Any] = {"title": title, "version": version}
- if summary:
- info["summary"] = summary
- if description:
- info["description"] = description
- if terms_of_service:
- info["termsOfService"] = terms_of_service
- if contact:
- info["contact"] = contact
- if license_info:
- info["license"] = license_info
- output: dict[str, Any] = {"openapi": openapi_version, "info": info}
- if servers:
- output["servers"] = servers
- components: dict[str, dict[str, Any]] = {}
- paths: dict[str, dict[str, Any]] = {}
- webhook_paths: dict[str, dict[str, Any]] = {}
- operation_ids: set[str] = set()
- all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or []))
- model_name_map = get_compat_model_name_map(all_fields)
- field_mapping, definitions = get_definitions(
- fields=all_fields,
- model_name_map=model_name_map,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- for route in routes or []:
- if isinstance(route, routing.APIRoute):
- result = get_openapi_path(
- route=route,
- operation_ids=operation_ids,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- if result:
- path, security_schemes, path_definitions = result
- if path:
- paths.setdefault(route.path_format, {}).update(path)
- if security_schemes:
- components.setdefault("securitySchemes", {}).update(
- security_schemes
- )
- if path_definitions:
- definitions.update(path_definitions)
- for webhook in webhooks or []:
- if isinstance(webhook, routing.APIRoute):
- result = get_openapi_path(
- route=webhook,
- operation_ids=operation_ids,
- model_name_map=model_name_map,
- field_mapping=field_mapping,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- if result:
- path, security_schemes, path_definitions = result
- if path:
- webhook_paths.setdefault(webhook.path_format, {}).update(path)
- if security_schemes:
- components.setdefault("securitySchemes", {}).update(
- security_schemes
- )
- if path_definitions:
- definitions.update(path_definitions)
- if definitions:
- components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
- if components:
- output["components"] = components
- output["paths"] = paths
- if webhook_paths:
- output["webhooks"] = webhook_paths
- if tags:
- output["tags"] = tags
- if external_docs:
- output["externalDocs"] = external_docs
- return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/param_functions.py b/backend/.venv/lib/python3.12/site-packages/fastapi/param_functions.py
deleted file mode 100644
index 0834fd7..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/param_functions.py
+++ /dev/null
@@ -1,2369 +0,0 @@
-from collections.abc import Sequence
-from typing import Annotated, Any, Callable, Optional, Union
-
-from annotated_doc import Doc
-from fastapi import params
-from fastapi._compat import Undefined
-from fastapi.openapi.models import Example
-from pydantic import AliasChoices, AliasPath
-from typing_extensions import Literal, deprecated
-
-_Unset: Any = Undefined
-
-
-def Path( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = ...,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- """
- Declare a path parameter for a *path operation*.
-
- Read more about it in the
- [FastAPI docs for Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/).
-
- ```python
- from typing import Annotated
-
- from fastapi import FastAPI, Path
-
- app = FastAPI()
-
-
- @app.get("/items/{item_id}")
- async def read_items(
- item_id: Annotated[int, Path(title="The ID of the item to get")],
- ):
- return {"item_id": item_id}
- ```
- """
- return params.Path(
- default=default,
- default_factory=default_factory,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Query( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.Query(
- default=default,
- default_factory=default_factory,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Header( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- convert_underscores: Annotated[
- bool,
- Doc(
- """
- Automatically convert underscores to hyphens in the parameter field name.
-
- Read more about it in the
- [FastAPI docs for Header Parameters](https://fastapi.tiangolo.com/tutorial/header-params/#automatic-conversion)
- """
- ),
- ] = True,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.Header(
- default=default,
- default_factory=default_factory,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- convert_underscores=convert_underscores,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Cookie( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.Cookie(
- default=default,
- default_factory=default_factory,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Body( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- embed: Annotated[
- Union[bool, None],
- Doc(
- """
- When `embed` is `True`, the parameter will be expected in a JSON body as a
- key instead of being the JSON body itself.
-
- This happens automatically when more than one `Body` parameter is declared.
-
- Read more about it in the
- [FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter).
- """
- ),
- ] = None,
- media_type: Annotated[
- str,
- Doc(
- """
- The media type of this parameter field. Changing it would affect the
- generated OpenAPI, but currently it doesn't affect the parsing of the data.
- """
- ),
- ] = "application/json",
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.Body(
- default=default,
- default_factory=default_factory,
- embed=embed,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Form( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- media_type: Annotated[
- str,
- Doc(
- """
- The media type of this parameter field. Changing it would affect the
- generated OpenAPI, but currently it doesn't affect the parsing of the data.
- """
- ),
- ] = "application/x-www-form-urlencoded",
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.Form(
- default=default,
- default_factory=default_factory,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def File( # noqa: N802
- default: Annotated[
- Any,
- Doc(
- """
- Default value if the parameter field is not set.
- """
- ),
- ] = Undefined,
- *,
- default_factory: Annotated[
- Union[Callable[[], Any], None],
- Doc(
- """
- A callable to generate the default value.
-
- This doesn't affect `Path` parameters as the value is always required.
- The parameter is available only for compatibility.
- """
- ),
- ] = _Unset,
- media_type: Annotated[
- str,
- Doc(
- """
- The media type of this parameter field. Changing it would affect the
- generated OpenAPI, but currently it doesn't affect the parsing of the data.
- """
- ),
- ] = "multipart/form-data",
- alias: Annotated[
- Optional[str],
- Doc(
- """
- An alternative name for the parameter field.
-
- This will be used to extract the data and for the generated OpenAPI.
- It is particularly useful when you can't use the name you want because it
- is a Python reserved keyword or similar.
- """
- ),
- ] = None,
- alias_priority: Annotated[
- Union[int, None],
- Doc(
- """
- Priority of the alias. This affects whether an alias generator is used.
- """
- ),
- ] = _Unset,
- validation_alias: Annotated[
- Union[str, AliasPath, AliasChoices, None],
- Doc(
- """
- 'Whitelist' validation step. The parameter field will be the single one
- allowed by the alias or set of aliases defined.
- """
- ),
- ] = None,
- serialization_alias: Annotated[
- Union[str, None],
- Doc(
- """
- 'Blacklist' validation step. The vanilla parameter field will be the
- single one of the alias' or set of aliases' fields and all the other
- fields will be ignored at serialization time.
- """
- ),
- ] = None,
- title: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable title.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Human-readable description.
- """
- ),
- ] = None,
- gt: Annotated[
- Optional[float],
- Doc(
- """
- Greater than. If set, value must be greater than this. Only applicable to
- numbers.
- """
- ),
- ] = None,
- ge: Annotated[
- Optional[float],
- Doc(
- """
- Greater than or equal. If set, value must be greater than or equal to
- this. Only applicable to numbers.
- """
- ),
- ] = None,
- lt: Annotated[
- Optional[float],
- Doc(
- """
- Less than. If set, value must be less than this. Only applicable to numbers.
- """
- ),
- ] = None,
- le: Annotated[
- Optional[float],
- Doc(
- """
- Less than or equal. If set, value must be less than or equal to this.
- Only applicable to numbers.
- """
- ),
- ] = None,
- min_length: Annotated[
- Optional[int],
- Doc(
- """
- Minimum length for strings.
- """
- ),
- ] = None,
- max_length: Annotated[
- Optional[int],
- Doc(
- """
- Maximum length for strings.
- """
- ),
- ] = None,
- pattern: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- ] = None,
- regex: Annotated[
- Optional[str],
- Doc(
- """
- RegEx pattern for strings.
- """
- ),
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Annotated[
- Union[str, None],
- Doc(
- """
- Parameter field name for discriminating the type in a tagged union.
- """
- ),
- ] = None,
- strict: Annotated[
- Union[bool, None],
- Doc(
- """
- If `True`, strict validation is applied to the field.
- """
- ),
- ] = _Unset,
- multiple_of: Annotated[
- Union[float, None],
- Doc(
- """
- Value must be a multiple of this. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- allow_inf_nan: Annotated[
- Union[bool, None],
- Doc(
- """
- Allow `inf`, `-inf`, `nan`. Only applicable to numbers.
- """
- ),
- ] = _Unset,
- max_digits: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of allow digits for strings.
- """
- ),
- ] = _Unset,
- decimal_places: Annotated[
- Union[int, None],
- Doc(
- """
- Maximum number of decimal places allowed for numbers.
- """
- ),
- ] = _Unset,
- examples: Annotated[
- Optional[list[Any]],
- Doc(
- """
- Example values for this field.
- """
- ),
- ] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Annotated[
- Optional[dict[str, Example]],
- Doc(
- """
- OpenAPI-specific examples.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Swagger UI (that provides the `/docs` interface) has better support for the
- OpenAPI-specific examples than the JSON Schema `examples`, that's the main
- use case for this.
-
- Read more about it in the
- [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Union[deprecated, str, bool, None],
- Doc(
- """
- Mark this parameter field as deprecated.
-
- It will affect the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) this parameter field in the generated OpenAPI.
- You probably don't need it, but it's available.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- json_schema_extra: Annotated[
- Union[dict[str, Any], None],
- Doc(
- """
- Any additional JSON schema data.
- """
- ),
- ] = None,
- **extra: Annotated[
- Any,
- Doc(
- """
- Include extra fields used by the JSON Schema.
- """
- ),
- deprecated(
- """
- The `extra` kwargs is deprecated. Use `json_schema_extra` instead.
- """
- ),
- ],
-) -> Any:
- return params.File(
- default=default,
- default_factory=default_factory,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- deprecated=deprecated,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-def Depends( # noqa: N802
- dependency: Annotated[
- Optional[Callable[..., Any]],
- Doc(
- """
- A "dependable" callable (like a function).
-
- Don't call it directly, FastAPI will call it for you, just pass the object
- directly.
- """
- ),
- ] = None,
- *,
- use_cache: Annotated[
- bool,
- Doc(
- """
- By default, after a dependency is called the first time in a request, if
- the dependency is declared again for the rest of the request (for example
- if the dependency is needed by several dependencies), the value will be
- re-used for the rest of the request.
-
- Set `use_cache` to `False` to disable this behavior and ensure the
- dependency is called again (if declared more than once) in the same request.
- """
- ),
- ] = True,
- scope: Annotated[
- Union[Literal["function", "request"], None],
- Doc(
- """
- Mainly for dependencies with `yield`, define when the dependency function
- should start (the code before `yield`) and when it should end (the code
- after `yield`).
-
- * `"function"`: start the dependency before the *path operation function*
- that handles the request, end the dependency after the *path operation
- function* ends, but **before** the response is sent back to the client.
- So, the dependency function will be executed **around** the *path operation
- **function***.
- * `"request"`: start the dependency before the *path operation function*
- that handles the request (similar to when using `"function"`), but end
- **after** the response is sent back to the client. So, the dependency
- function will be executed **around** the **request** and response cycle.
- """
- ),
- ] = None,
-) -> Any:
- """
- Declare a FastAPI dependency.
-
- It takes a single "dependable" callable (like a function).
-
- Don't call it directly, FastAPI will call it for you.
-
- Read more about it in the
- [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/).
-
- **Example**
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
-
- app = FastAPI()
-
-
- async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
- return {"q": q, "skip": skip, "limit": limit}
-
-
- @app.get("/items/")
- async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
- return commons
- ```
- """
- return params.Depends(dependency=dependency, use_cache=use_cache, scope=scope)
-
-
-def Security( # noqa: N802
- dependency: Annotated[
- Optional[Callable[..., Any]],
- Doc(
- """
- A "dependable" callable (like a function).
-
- Don't call it directly, FastAPI will call it for you, just pass the object
- directly.
- """
- ),
- ] = None,
- *,
- scopes: Annotated[
- Optional[Sequence[str]],
- Doc(
- """
- OAuth2 scopes required for the *path operation* that uses this Security
- dependency.
-
- The term "scope" comes from the OAuth2 specification, it seems to be
- intentionally vague and interpretable. It normally refers to permissions,
- in cases to roles.
-
- These scopes are integrated with OpenAPI (and the API docs at `/docs`).
- So they are visible in the OpenAPI specification.
- )
- """
- ),
- ] = None,
- use_cache: Annotated[
- bool,
- Doc(
- """
- By default, after a dependency is called the first time in a request, if
- the dependency is declared again for the rest of the request (for example
- if the dependency is needed by several dependencies), the value will be
- re-used for the rest of the request.
-
- Set `use_cache` to `False` to disable this behavior and ensure the
- dependency is called again (if declared more than once) in the same request.
- """
- ),
- ] = True,
-) -> Any:
- """
- Declare a FastAPI Security dependency.
-
- The only difference with a regular dependency is that it can declare OAuth2
- scopes that will be integrated with OpenAPI and the automatic UI docs (by default
- at `/docs`).
-
- It takes a single "dependable" callable (like a function).
-
- Don't call it directly, FastAPI will call it for you.
-
- Read more about it in the
- [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/) and
- in the
- [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
-
- **Example**
-
- ```python
- from typing import Annotated
-
- from fastapi import Security, FastAPI
-
- from .db import User
- from .security import get_current_active_user
-
- app = FastAPI()
-
- @app.get("/users/me/items/")
- async def read_own_items(
- current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
- ):
- return [{"item_id": "Foo", "owner": current_user.username}]
- ```
- """
- return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/params.py b/backend/.venv/lib/python3.12/site-packages/fastapi/params.py
deleted file mode 100644
index 72e797f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/params.py
+++ /dev/null
@@ -1,755 +0,0 @@
-import warnings
-from collections.abc import Sequence
-from dataclasses import dataclass
-from enum import Enum
-from typing import Annotated, Any, Callable, Optional, Union
-
-from fastapi.exceptions import FastAPIDeprecationWarning
-from fastapi.openapi.models import Example
-from pydantic import AliasChoices, AliasPath
-from pydantic.fields import FieldInfo
-from typing_extensions import Literal, deprecated
-
-from ._compat import (
- Undefined,
-)
-
-_Unset: Any = Undefined
-
-
-class ParamTypes(Enum):
- query = "query"
- header = "header"
- path = "path"
- cookie = "cookie"
-
-
-class Param(FieldInfo): # type: ignore[misc]
- in_: ParamTypes
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- if example is not _Unset:
- warnings.warn(
- "`example` has been deprecated, please use `examples` instead",
- category=FastAPIDeprecationWarning,
- stacklevel=4,
- )
- self.example = example
- self.include_in_schema = include_in_schema
- self.openapi_examples = openapi_examples
- kwargs = dict(
- default=default,
- default_factory=default_factory,
- alias=alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- discriminator=discriminator,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- **extra,
- )
- if examples is not None:
- kwargs["examples"] = examples
- if regex is not None:
- warnings.warn(
- "`regex` has been deprecated, please use `pattern` instead",
- category=FastAPIDeprecationWarning,
- stacklevel=4,
- )
- current_json_schema_extra = json_schema_extra or extra
- kwargs["deprecated"] = deprecated
-
- if serialization_alias in (_Unset, None) and isinstance(alias, str):
- serialization_alias = alias
- if validation_alias in (_Unset, None):
- validation_alias = alias
- kwargs.update(
- {
- "annotation": annotation,
- "alias_priority": alias_priority,
- "validation_alias": validation_alias,
- "serialization_alias": serialization_alias,
- "strict": strict,
- "json_schema_extra": current_json_schema_extra,
- }
- )
- kwargs["pattern"] = pattern or regex
-
- use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset}
-
- super().__init__(**use_kwargs)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.default})"
-
-
-class Path(Param): # type: ignore[misc]
- in_ = ParamTypes.path
-
- def __init__(
- self,
- default: Any = ...,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- assert default is ..., "Path parameters cannot have a default value"
- self.in_ = self.in_
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Query(Param): # type: ignore[misc]
- in_ = ParamTypes.query
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Header(Param): # type: ignore[misc]
- in_ = ParamTypes.header
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- convert_underscores: bool = True,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- self.convert_underscores = convert_underscores
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Cookie(Param): # type: ignore[misc]
- in_ = ParamTypes.cookie
-
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class Body(FieldInfo): # type: ignore[misc]
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- embed: Union[bool, None] = None,
- media_type: str = "application/json",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- self.embed = embed
- self.media_type = media_type
- if example is not _Unset:
- warnings.warn(
- "`example` has been deprecated, please use `examples` instead",
- category=FastAPIDeprecationWarning,
- stacklevel=4,
- )
- self.example = example
- self.include_in_schema = include_in_schema
- self.openapi_examples = openapi_examples
- kwargs = dict(
- default=default,
- default_factory=default_factory,
- alias=alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- discriminator=discriminator,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- **extra,
- )
- if examples is not None:
- kwargs["examples"] = examples
- if regex is not None:
- warnings.warn(
- "`regex` has been deprecated, please use `pattern` instead",
- category=FastAPIDeprecationWarning,
- stacklevel=4,
- )
- current_json_schema_extra = json_schema_extra or extra
- kwargs["deprecated"] = deprecated
- if serialization_alias in (_Unset, None) and isinstance(alias, str):
- serialization_alias = alias
- if validation_alias in (_Unset, None):
- validation_alias = alias
- kwargs.update(
- {
- "annotation": annotation,
- "alias_priority": alias_priority,
- "validation_alias": validation_alias,
- "serialization_alias": serialization_alias,
- "strict": strict,
- "json_schema_extra": current_json_schema_extra,
- }
- )
- kwargs["pattern"] = pattern or regex
-
- use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset}
-
- super().__init__(**use_kwargs)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.default})"
-
-
-class Form(Body): # type: ignore[misc]
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- media_type: str = "application/x-www-form-urlencoded",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-class File(Form): # type: ignore[misc]
- def __init__(
- self,
- default: Any = Undefined,
- *,
- default_factory: Union[Callable[[], Any], None] = _Unset,
- annotation: Optional[Any] = None,
- media_type: str = "multipart/form-data",
- alias: Optional[str] = None,
- alias_priority: Union[int, None] = _Unset,
- validation_alias: Union[str, AliasPath, AliasChoices, None] = None,
- serialization_alias: Union[str, None] = None,
- title: Optional[str] = None,
- description: Optional[str] = None,
- gt: Optional[float] = None,
- ge: Optional[float] = None,
- lt: Optional[float] = None,
- le: Optional[float] = None,
- min_length: Optional[int] = None,
- max_length: Optional[int] = None,
- pattern: Optional[str] = None,
- regex: Annotated[
- Optional[str],
- deprecated(
- "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
- ),
- ] = None,
- discriminator: Union[str, None] = None,
- strict: Union[bool, None] = _Unset,
- multiple_of: Union[float, None] = _Unset,
- allow_inf_nan: Union[bool, None] = _Unset,
- max_digits: Union[int, None] = _Unset,
- decimal_places: Union[int, None] = _Unset,
- examples: Optional[list[Any]] = None,
- example: Annotated[
- Optional[Any],
- deprecated(
- "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
- "although still supported. Use examples instead."
- ),
- ] = _Unset,
- openapi_examples: Optional[dict[str, Example]] = None,
- deprecated: Union[deprecated, str, bool, None] = None,
- include_in_schema: bool = True,
- json_schema_extra: Union[dict[str, Any], None] = None,
- **extra: Any,
- ):
- super().__init__(
- default=default,
- default_factory=default_factory,
- annotation=annotation,
- media_type=media_type,
- alias=alias,
- alias_priority=alias_priority,
- validation_alias=validation_alias,
- serialization_alias=serialization_alias,
- title=title,
- description=description,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- min_length=min_length,
- max_length=max_length,
- pattern=pattern,
- regex=regex,
- discriminator=discriminator,
- strict=strict,
- multiple_of=multiple_of,
- allow_inf_nan=allow_inf_nan,
- max_digits=max_digits,
- decimal_places=decimal_places,
- deprecated=deprecated,
- example=example,
- examples=examples,
- openapi_examples=openapi_examples,
- include_in_schema=include_in_schema,
- json_schema_extra=json_schema_extra,
- **extra,
- )
-
-
-@dataclass(frozen=True)
-class Depends:
- dependency: Optional[Callable[..., Any]] = None
- use_cache: bool = True
- scope: Union[Literal["function", "request"], None] = None
-
-
-@dataclass(frozen=True)
-class Security(Depends):
- scopes: Optional[Sequence[str]] = None
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/py.typed b/backend/.venv/lib/python3.12/site-packages/fastapi/py.typed
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/requests.py b/backend/.venv/lib/python3.12/site-packages/fastapi/requests.py
deleted file mode 100644
index d16552c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/requests.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from starlette.requests import HTTPConnection as HTTPConnection # noqa: F401
-from starlette.requests import Request as Request # noqa: F401
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/responses.py b/backend/.venv/lib/python3.12/site-packages/fastapi/responses.py
deleted file mode 100644
index 6c8db6f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/responses.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from typing import Any
-
-from starlette.responses import FileResponse as FileResponse # noqa
-from starlette.responses import HTMLResponse as HTMLResponse # noqa
-from starlette.responses import JSONResponse as JSONResponse # noqa
-from starlette.responses import PlainTextResponse as PlainTextResponse # noqa
-from starlette.responses import RedirectResponse as RedirectResponse # noqa
-from starlette.responses import Response as Response # noqa
-from starlette.responses import StreamingResponse as StreamingResponse # noqa
-
-try:
- import ujson
-except ImportError: # pragma: nocover
- ujson = None # type: ignore
-
-
-try:
- import orjson
-except ImportError: # pragma: nocover
- orjson = None # type: ignore
-
-
-class UJSONResponse(JSONResponse):
- """
- JSON response using the high-performance ujson library to serialize data to JSON.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
- """
-
- def render(self, content: Any) -> bytes:
- assert ujson is not None, "ujson must be installed to use UJSONResponse"
- return ujson.dumps(content, ensure_ascii=False).encode("utf-8")
-
-
-class ORJSONResponse(JSONResponse):
- """
- JSON response using the high-performance orjson library to serialize data to JSON.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
- """
-
- def render(self, content: Any) -> bytes:
- assert orjson is not None, "orjson must be installed to use ORJSONResponse"
- return orjson.dumps(
- content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY
- )
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/routing.py b/backend/.venv/lib/python3.12/site-packages/fastapi/routing.py
deleted file mode 100644
index 9ca2f46..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/routing.py
+++ /dev/null
@@ -1,4508 +0,0 @@
-import email.message
-import functools
-import inspect
-import json
-from collections.abc import (
- AsyncIterator,
- Awaitable,
- Collection,
- Coroutine,
- Mapping,
- Sequence,
-)
-from contextlib import AsyncExitStack, asynccontextmanager
-from enum import Enum, IntEnum
-from typing import (
- Annotated,
- Any,
- Callable,
- Optional,
- Union,
-)
-
-from annotated_doc import Doc
-from fastapi import params
-from fastapi._compat import (
- ModelField,
- Undefined,
- annotation_is_pydantic_v1,
- lenient_issubclass,
-)
-from fastapi.datastructures import Default, DefaultPlaceholder
-from fastapi.dependencies.models import Dependant
-from fastapi.dependencies.utils import (
- _should_embed_body_fields,
- get_body_field,
- get_dependant,
- get_flat_dependant,
- get_parameterless_sub_dependant,
- get_typed_return_annotation,
- solve_dependencies,
-)
-from fastapi.encoders import jsonable_encoder
-from fastapi.exceptions import (
- EndpointContext,
- FastAPIError,
- PydanticV1NotSupportedError,
- RequestValidationError,
- ResponseValidationError,
- WebSocketRequestValidationError,
-)
-from fastapi.types import DecoratedCallable, IncEx
-from fastapi.utils import (
- create_cloned_field,
- create_model_field,
- generate_unique_id,
- get_value_or_default,
- is_body_allowed_for_status_code,
-)
-from starlette import routing
-from starlette._exception_handler import wrap_app_handling_exceptions
-from starlette._utils import is_async_callable
-from starlette.concurrency import run_in_threadpool
-from starlette.exceptions import HTTPException
-from starlette.requests import Request
-from starlette.responses import JSONResponse, Response
-from starlette.routing import (
- BaseRoute,
- Match,
- compile_path,
- get_name,
-)
-from starlette.routing import Mount as Mount # noqa
-from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
-from starlette.websockets import WebSocket
-from typing_extensions import deprecated
-
-
-# Copy of starlette.routing.request_response modified to include the
-# dependencies' AsyncExitStack
-def request_response(
- func: Callable[[Request], Union[Awaitable[Response], Response]],
-) -> ASGIApp:
- """
- Takes a function or coroutine `func(request) -> response`,
- and returns an ASGI application.
- """
- f: Callable[[Request], Awaitable[Response]] = (
- func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore
- )
-
- async def app(scope: Scope, receive: Receive, send: Send) -> None:
- request = Request(scope, receive, send)
-
- async def app(scope: Scope, receive: Receive, send: Send) -> None:
- # Starts customization
- response_awaited = False
- async with AsyncExitStack() as request_stack:
- scope["fastapi_inner_astack"] = request_stack
- async with AsyncExitStack() as function_stack:
- scope["fastapi_function_astack"] = function_stack
- response = await f(request)
- await response(scope, receive, send)
- # Continues customization
- response_awaited = True
- if not response_awaited:
- raise FastAPIError(
- "Response not awaited. There's a high chance that the "
- "application code is raising an exception and a dependency with yield "
- "has a block with a bare except, or a block with except Exception, "
- "and is not raising the exception again. Read more about it in the "
- "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except"
- )
-
- # Same as in Starlette
- await wrap_app_handling_exceptions(app, request)(scope, receive, send)
-
- return app
-
-
-# Copy of starlette.routing.websocket_session modified to include the
-# dependencies' AsyncExitStack
-def websocket_session(
- func: Callable[[WebSocket], Awaitable[None]],
-) -> ASGIApp:
- """
- Takes a coroutine `func(session)`, and returns an ASGI application.
- """
- # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async"
-
- async def app(scope: Scope, receive: Receive, send: Send) -> None:
- session = WebSocket(scope, receive=receive, send=send)
-
- async def app(scope: Scope, receive: Receive, send: Send) -> None:
- async with AsyncExitStack() as request_stack:
- scope["fastapi_inner_astack"] = request_stack
- async with AsyncExitStack() as function_stack:
- scope["fastapi_function_astack"] = function_stack
- await func(session)
-
- # Same as in Starlette
- await wrap_app_handling_exceptions(app, session)(scope, receive, send)
-
- return app
-
-
-def _merge_lifespan_context(
- original_context: Lifespan[Any], nested_context: Lifespan[Any]
-) -> Lifespan[Any]:
- @asynccontextmanager
- async def merged_lifespan(
- app: AppType,
- ) -> AsyncIterator[Optional[Mapping[str, Any]]]:
- async with original_context(app) as maybe_original_state:
- async with nested_context(app) as maybe_nested_state:
- if maybe_nested_state is None and maybe_original_state is None:
- yield None # old ASGI compatibility
- else:
- yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}
-
- return merged_lifespan # type: ignore[return-value]
-
-
-# Cache for endpoint context to avoid re-extracting on every request
-_endpoint_context_cache: dict[int, EndpointContext] = {}
-
-
-def _extract_endpoint_context(func: Any) -> EndpointContext:
- """Extract endpoint context with caching to avoid repeated file I/O."""
- func_id = id(func)
-
- if func_id in _endpoint_context_cache:
- return _endpoint_context_cache[func_id]
-
- try:
- ctx: EndpointContext = {}
-
- if (source_file := inspect.getsourcefile(func)) is not None:
- ctx["file"] = source_file
- if (line_number := inspect.getsourcelines(func)[1]) is not None:
- ctx["line"] = line_number
- if (func_name := getattr(func, "__name__", None)) is not None:
- ctx["function"] = func_name
- except Exception:
- ctx = EndpointContext()
-
- _endpoint_context_cache[func_id] = ctx
- return ctx
-
-
-async def serialize_response(
- *,
- field: Optional[ModelField] = None,
- response_content: Any,
- include: Optional[IncEx] = None,
- exclude: Optional[IncEx] = None,
- by_alias: bool = True,
- exclude_unset: bool = False,
- exclude_defaults: bool = False,
- exclude_none: bool = False,
- is_coroutine: bool = True,
- endpoint_ctx: Optional[EndpointContext] = None,
-) -> Any:
- if field:
- errors = []
- if is_coroutine:
- value, errors_ = field.validate(response_content, {}, loc=("response",))
- else:
- value, errors_ = await run_in_threadpool(
- field.validate, response_content, {}, loc=("response",)
- )
- if isinstance(errors_, list):
- errors.extend(errors_)
- if errors:
- ctx = endpoint_ctx or EndpointContext()
- raise ResponseValidationError(
- errors=errors,
- body=response_content,
- endpoint_ctx=ctx,
- )
-
- return field.serialize(
- value,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
-
- else:
- return jsonable_encoder(response_content)
-
-
-async def run_endpoint_function(
- *, dependant: Dependant, values: dict[str, Any], is_coroutine: bool
-) -> Any:
- # Only called by get_request_handler. Has been split into its own function to
- # facilitate profiling endpoints, since inner functions are harder to profile.
- assert dependant.call is not None, "dependant.call must be a function"
-
- if is_coroutine:
- return await dependant.call(**values)
- else:
- return await run_in_threadpool(dependant.call, **values)
-
-
-def get_request_handler(
- dependant: Dependant,
- body_field: Optional[ModelField] = None,
- status_code: Optional[int] = None,
- response_class: Union[type[Response], DefaultPlaceholder] = Default(JSONResponse),
- response_field: Optional[ModelField] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- dependency_overrides_provider: Optional[Any] = None,
- embed_body_fields: bool = False,
-) -> Callable[[Request], Coroutine[Any, Any, Response]]:
- assert dependant.call is not None, "dependant.call must be a function"
- is_coroutine = dependant.is_coroutine_callable
- is_body_form = body_field and isinstance(body_field.field_info, params.Form)
- if isinstance(response_class, DefaultPlaceholder):
- actual_response_class: type[Response] = response_class.value
- else:
- actual_response_class = response_class
-
- async def app(request: Request) -> Response:
- response: Union[Response, None] = None
- file_stack = request.scope.get("fastapi_middleware_astack")
- assert isinstance(file_stack, AsyncExitStack), (
- "fastapi_middleware_astack not found in request scope"
- )
-
- # Extract endpoint context for error messages
- endpoint_ctx = (
- _extract_endpoint_context(dependant.call)
- if dependant.call
- else EndpointContext()
- )
-
- if dependant.path:
- # For mounted sub-apps, include the mount path prefix
- mount_path = request.scope.get("root_path", "").rstrip("/")
- endpoint_ctx["path"] = f"{request.method} {mount_path}{dependant.path}"
-
- # Read body and auto-close files
- try:
- body: Any = None
- if body_field:
- if is_body_form:
- body = await request.form()
- file_stack.push_async_callback(body.close)
- else:
- body_bytes = await request.body()
- if body_bytes:
- json_body: Any = Undefined
- content_type_value = request.headers.get("content-type")
- if not content_type_value:
- json_body = await request.json()
- else:
- message = email.message.Message()
- message["content-type"] = content_type_value
- if message.get_content_maintype() == "application":
- subtype = message.get_content_subtype()
- if subtype == "json" or subtype.endswith("+json"):
- json_body = await request.json()
- if json_body != Undefined:
- body = json_body
- else:
- body = body_bytes
- except json.JSONDecodeError as e:
- validation_error = RequestValidationError(
- [
- {
- "type": "json_invalid",
- "loc": ("body", e.pos),
- "msg": "JSON decode error",
- "input": {},
- "ctx": {"error": e.msg},
- }
- ],
- body=e.doc,
- endpoint_ctx=endpoint_ctx,
- )
- raise validation_error from e
- except HTTPException:
- # If a middleware raises an HTTPException, it should be raised again
- raise
- except Exception as e:
- http_error = HTTPException(
- status_code=400, detail="There was an error parsing the body"
- )
- raise http_error from e
-
- # Solve dependencies and run path operation function, auto-closing dependencies
- errors: list[Any] = []
- async_exit_stack = request.scope.get("fastapi_inner_astack")
- assert isinstance(async_exit_stack, AsyncExitStack), (
- "fastapi_inner_astack not found in request scope"
- )
- solved_result = await solve_dependencies(
- request=request,
- dependant=dependant,
- body=body,
- dependency_overrides_provider=dependency_overrides_provider,
- async_exit_stack=async_exit_stack,
- embed_body_fields=embed_body_fields,
- )
- errors = solved_result.errors
- if not errors:
- raw_response = await run_endpoint_function(
- dependant=dependant,
- values=solved_result.values,
- is_coroutine=is_coroutine,
- )
- if isinstance(raw_response, Response):
- if raw_response.background is None:
- raw_response.background = solved_result.background_tasks
- response = raw_response
- else:
- response_args: dict[str, Any] = {
- "background": solved_result.background_tasks
- }
- # If status_code was set, use it, otherwise use the default from the
- # response class, in the case of redirect it's 307
- current_status_code = (
- status_code if status_code else solved_result.response.status_code
- )
- if current_status_code is not None:
- response_args["status_code"] = current_status_code
- if solved_result.response.status_code:
- response_args["status_code"] = solved_result.response.status_code
- content = await serialize_response(
- field=response_field,
- response_content=raw_response,
- include=response_model_include,
- exclude=response_model_exclude,
- by_alias=response_model_by_alias,
- exclude_unset=response_model_exclude_unset,
- exclude_defaults=response_model_exclude_defaults,
- exclude_none=response_model_exclude_none,
- is_coroutine=is_coroutine,
- endpoint_ctx=endpoint_ctx,
- )
- response = actual_response_class(content, **response_args)
- if not is_body_allowed_for_status_code(response.status_code):
- response.body = b""
- response.headers.raw.extend(solved_result.response.headers.raw)
- if errors:
- validation_error = RequestValidationError(
- errors, body=body, endpoint_ctx=endpoint_ctx
- )
- raise validation_error
-
- # Return response
- assert response
- return response
-
- return app
-
-
-def get_websocket_app(
- dependant: Dependant,
- dependency_overrides_provider: Optional[Any] = None,
- embed_body_fields: bool = False,
-) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
- async def app(websocket: WebSocket) -> None:
- endpoint_ctx = (
- _extract_endpoint_context(dependant.call)
- if dependant.call
- else EndpointContext()
- )
- if dependant.path:
- # For mounted sub-apps, include the mount path prefix
- mount_path = websocket.scope.get("root_path", "").rstrip("/")
- endpoint_ctx["path"] = f"WS {mount_path}{dependant.path}"
- async_exit_stack = websocket.scope.get("fastapi_inner_astack")
- assert isinstance(async_exit_stack, AsyncExitStack), (
- "fastapi_inner_astack not found in request scope"
- )
- solved_result = await solve_dependencies(
- request=websocket,
- dependant=dependant,
- dependency_overrides_provider=dependency_overrides_provider,
- async_exit_stack=async_exit_stack,
- embed_body_fields=embed_body_fields,
- )
- if solved_result.errors:
- raise WebSocketRequestValidationError(
- solved_result.errors,
- endpoint_ctx=endpoint_ctx,
- )
- assert dependant.call is not None, "dependant.call must be a function"
- await dependant.call(**solved_result.values)
-
- return app
-
-
-class APIWebSocketRoute(routing.WebSocketRoute):
- def __init__(
- self,
- path: str,
- endpoint: Callable[..., Any],
- *,
- name: Optional[str] = None,
- dependencies: Optional[Sequence[params.Depends]] = None,
- dependency_overrides_provider: Optional[Any] = None,
- ) -> None:
- self.path = path
- self.endpoint = endpoint
- self.name = get_name(endpoint) if name is None else name
- self.dependencies = list(dependencies or [])
- self.path_regex, self.path_format, self.param_convertors = compile_path(path)
- self.dependant = get_dependant(
- path=self.path_format, call=self.endpoint, scope="function"
- )
- for depends in self.dependencies[::-1]:
- self.dependant.dependencies.insert(
- 0,
- get_parameterless_sub_dependant(depends=depends, path=self.path_format),
- )
- self._flat_dependant = get_flat_dependant(self.dependant)
- self._embed_body_fields = _should_embed_body_fields(
- self._flat_dependant.body_params
- )
- self.app = websocket_session(
- get_websocket_app(
- dependant=self.dependant,
- dependency_overrides_provider=dependency_overrides_provider,
- embed_body_fields=self._embed_body_fields,
- )
- )
-
- def matches(self, scope: Scope) -> tuple[Match, Scope]:
- match, child_scope = super().matches(scope)
- if match != Match.NONE:
- child_scope["route"] = self
- return match, child_scope
-
-
-class APIRoute(routing.Route):
- def __init__(
- self,
- path: str,
- endpoint: Callable[..., Any],
- *,
- response_model: Any = Default(None),
- status_code: Optional[int] = None,
- tags: Optional[list[Union[str, Enum]]] = None,
- dependencies: Optional[Sequence[params.Depends]] = None,
- summary: Optional[str] = None,
- description: Optional[str] = None,
- response_description: str = "Successful Response",
- responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
- deprecated: Optional[bool] = None,
- name: Optional[str] = None,
- methods: Optional[Union[set[str], list[str]]] = None,
- operation_id: Optional[str] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- include_in_schema: bool = True,
- response_class: Union[type[Response], DefaultPlaceholder] = Default(
- JSONResponse
- ),
- dependency_overrides_provider: Optional[Any] = None,
- callbacks: Optional[list[BaseRoute]] = None,
- openapi_extra: Optional[dict[str, Any]] = None,
- generate_unique_id_function: Union[
- Callable[["APIRoute"], str], DefaultPlaceholder
- ] = Default(generate_unique_id),
- ) -> None:
- self.path = path
- self.endpoint = endpoint
- if isinstance(response_model, DefaultPlaceholder):
- return_annotation = get_typed_return_annotation(endpoint)
- if lenient_issubclass(return_annotation, Response):
- response_model = None
- else:
- response_model = return_annotation
- self.response_model = response_model
- self.summary = summary
- self.response_description = response_description
- self.deprecated = deprecated
- self.operation_id = operation_id
- self.response_model_include = response_model_include
- self.response_model_exclude = response_model_exclude
- self.response_model_by_alias = response_model_by_alias
- self.response_model_exclude_unset = response_model_exclude_unset
- self.response_model_exclude_defaults = response_model_exclude_defaults
- self.response_model_exclude_none = response_model_exclude_none
- self.include_in_schema = include_in_schema
- self.response_class = response_class
- self.dependency_overrides_provider = dependency_overrides_provider
- self.callbacks = callbacks
- self.openapi_extra = openapi_extra
- self.generate_unique_id_function = generate_unique_id_function
- self.tags = tags or []
- self.responses = responses or {}
- self.name = get_name(endpoint) if name is None else name
- self.path_regex, self.path_format, self.param_convertors = compile_path(path)
- if methods is None:
- methods = ["GET"]
- self.methods: set[str] = {method.upper() for method in methods}
- if isinstance(generate_unique_id_function, DefaultPlaceholder):
- current_generate_unique_id: Callable[[APIRoute], str] = (
- generate_unique_id_function.value
- )
- else:
- current_generate_unique_id = generate_unique_id_function
- self.unique_id = self.operation_id or current_generate_unique_id(self)
- # normalize enums e.g. http.HTTPStatus
- if isinstance(status_code, IntEnum):
- status_code = int(status_code)
- self.status_code = status_code
- if self.response_model:
- assert is_body_allowed_for_status_code(status_code), (
- f"Status code {status_code} must not have a response body"
- )
- response_name = "Response_" + self.unique_id
- if annotation_is_pydantic_v1(self.response_model):
- raise PydanticV1NotSupportedError(
- "pydantic.v1 models are no longer supported by FastAPI."
- f" Please update the response model {self.response_model!r}."
- )
- self.response_field = create_model_field(
- name=response_name,
- type_=self.response_model,
- mode="serialization",
- )
- # Create a clone of the field, so that a Pydantic submodel is not returned
- # as is just because it's an instance of a subclass of a more limited class
- # e.g. UserInDB (containing hashed_password) could be a subclass of User
- # that doesn't have the hashed_password. But because it's a subclass, it
- # would pass the validation and be returned as is.
- # By being a new field, no inheritance will be passed as is. A new model
- # will always be created.
- # TODO: remove when deprecating Pydantic v1
- self.secure_cloned_response_field: Optional[ModelField] = (
- create_cloned_field(self.response_field)
- )
- else:
- self.response_field = None # type: ignore
- self.secure_cloned_response_field = None
- self.dependencies = list(dependencies or [])
- self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
- # if a "form feed" character (page break) is found in the description text,
- # truncate description text to the content preceding the first "form feed"
- self.description = self.description.split("\f")[0].strip()
- response_fields = {}
- for additional_status_code, response in self.responses.items():
- assert isinstance(response, dict), "An additional response must be a dict"
- model = response.get("model")
- if model:
- assert is_body_allowed_for_status_code(additional_status_code), (
- f"Status code {additional_status_code} must not have a response body"
- )
- response_name = f"Response_{additional_status_code}_{self.unique_id}"
- if annotation_is_pydantic_v1(model):
- raise PydanticV1NotSupportedError(
- "pydantic.v1 models are no longer supported by FastAPI."
- f" In responses={{}}, please update {model}."
- )
- response_field = create_model_field(
- name=response_name, type_=model, mode="serialization"
- )
- response_fields[additional_status_code] = response_field
- if response_fields:
- self.response_fields: dict[Union[int, str], ModelField] = response_fields
- else:
- self.response_fields = {}
-
- assert callable(endpoint), "An endpoint must be a callable"
- self.dependant = get_dependant(
- path=self.path_format, call=self.endpoint, scope="function"
- )
- for depends in self.dependencies[::-1]:
- self.dependant.dependencies.insert(
- 0,
- get_parameterless_sub_dependant(depends=depends, path=self.path_format),
- )
- self._flat_dependant = get_flat_dependant(self.dependant)
- self._embed_body_fields = _should_embed_body_fields(
- self._flat_dependant.body_params
- )
- self.body_field = get_body_field(
- flat_dependant=self._flat_dependant,
- name=self.unique_id,
- embed_body_fields=self._embed_body_fields,
- )
- self.app = request_response(self.get_route_handler())
-
- def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
- return get_request_handler(
- dependant=self.dependant,
- body_field=self.body_field,
- status_code=self.status_code,
- response_class=self.response_class,
- response_field=self.secure_cloned_response_field,
- response_model_include=self.response_model_include,
- response_model_exclude=self.response_model_exclude,
- response_model_by_alias=self.response_model_by_alias,
- response_model_exclude_unset=self.response_model_exclude_unset,
- response_model_exclude_defaults=self.response_model_exclude_defaults,
- response_model_exclude_none=self.response_model_exclude_none,
- dependency_overrides_provider=self.dependency_overrides_provider,
- embed_body_fields=self._embed_body_fields,
- )
-
- def matches(self, scope: Scope) -> tuple[Match, Scope]:
- match, child_scope = super().matches(scope)
- if match != Match.NONE:
- child_scope["route"] = self
- return match, child_scope
-
-
-class APIRouter(routing.Router):
- """
- `APIRouter` class, used to group *path operations*, for example to structure
- an app in multiple files. It would then be included in the `FastAPI` app, or
- in another `APIRouter` (ultimately included in the app).
-
- Read more about it in the
- [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
-
- app = FastAPI()
- router = APIRouter()
-
-
- @router.get("/users/", tags=["users"])
- async def read_users():
- return [{"username": "Rick"}, {"username": "Morty"}]
-
-
- app.include_router(router)
- ```
- """
-
- def __init__(
- self,
- *,
- prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "",
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to all the *path operations* in this
- router.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to all the
- *path operations* in this router.
-
- Read more about it in the
- [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- default_response_class: Annotated[
- type[Response],
- Doc(
- """
- The default response class to be used.
-
- Read more in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
- """
- ),
- ] = Default(JSONResponse),
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses to be shown in OpenAPI.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
-
- And in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- OpenAPI callbacks that should apply to all *path operations* in this
- router.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- routes: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- **Note**: you probably shouldn't use this parameter, it is inherited
- from Starlette and supported for compatibility.
-
- ---
-
- A list of routes to serve incoming HTTP and WebSocket requests.
- """
- ),
- deprecated(
- """
- You normally wouldn't use this parameter with FastAPI, it is inherited
- from Starlette and supported for compatibility.
-
- In FastAPI, you normally would use the *path operation methods*,
- like `router.get()`, `router.post()`, etc.
- """
- ),
- ] = None,
- redirect_slashes: Annotated[
- bool,
- Doc(
- """
- Whether to detect and redirect slashes in URLs when the client doesn't
- use the same format.
- """
- ),
- ] = True,
- default: Annotated[
- Optional[ASGIApp],
- Doc(
- """
- Default function handler for this router. Used to handle
- 404 Not Found errors.
- """
- ),
- ] = None,
- dependency_overrides_provider: Annotated[
- Optional[Any],
- Doc(
- """
- Only used internally by FastAPI to handle dependency overrides.
-
- You shouldn't need to use it. It normally points to the `FastAPI` app
- object.
- """
- ),
- ] = None,
- route_class: Annotated[
- type[APIRoute],
- Doc(
- """
- Custom route (*path operation*) class to be used by this router.
-
- Read more about it in the
- [FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router).
- """
- ),
- ] = APIRoute,
- on_startup: Annotated[
- Optional[Sequence[Callable[[], Any]]],
- Doc(
- """
- A list of startup event handler functions.
-
- You should instead use the `lifespan` handlers.
-
- Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- on_shutdown: Annotated[
- Optional[Sequence[Callable[[], Any]]],
- Doc(
- """
- A list of shutdown event handler functions.
-
- You should instead use the `lifespan` handlers.
-
- Read more in the
- [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- # the generic to Lifespan[AppType] is the type of the top level application
- # which the router cannot know statically, so we use typing.Any
- lifespan: Annotated[
- Optional[Lifespan[Any]],
- Doc(
- """
- A `Lifespan` context manager handler. This replaces `startup` and
- `shutdown` functions with a single context manager.
-
- Read more in the
- [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark all *path operations* in this router as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- To include (or not) all the *path operations* in this router in the
- generated OpenAPI.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> None:
- super().__init__(
- routes=routes,
- redirect_slashes=redirect_slashes,
- default=default,
- on_startup=on_startup,
- on_shutdown=on_shutdown,
- lifespan=lifespan,
- )
- if prefix:
- assert prefix.startswith("/"), "A path prefix must start with '/'"
- assert not prefix.endswith("/"), (
- "A path prefix must not end with '/', as the routes will start with '/'"
- )
- self.prefix = prefix
- self.tags: list[Union[str, Enum]] = tags or []
- self.dependencies = list(dependencies or [])
- self.deprecated = deprecated
- self.include_in_schema = include_in_schema
- self.responses = responses or {}
- self.callbacks = callbacks or []
- self.dependency_overrides_provider = dependency_overrides_provider
- self.route_class = route_class
- self.default_response_class = default_response_class
- self.generate_unique_id_function = generate_unique_id_function
-
- def route(
- self,
- path: str,
- methods: Optional[Collection[str]] = None,
- name: Optional[str] = None,
- include_in_schema: bool = True,
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_route(
- path,
- func,
- methods=methods,
- name=name,
- include_in_schema=include_in_schema,
- )
- return func
-
- return decorator
-
- def add_api_route(
- self,
- path: str,
- endpoint: Callable[..., Any],
- *,
- response_model: Any = Default(None),
- status_code: Optional[int] = None,
- tags: Optional[list[Union[str, Enum]]] = None,
- dependencies: Optional[Sequence[params.Depends]] = None,
- summary: Optional[str] = None,
- description: Optional[str] = None,
- response_description: str = "Successful Response",
- responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
- deprecated: Optional[bool] = None,
- methods: Optional[Union[set[str], list[str]]] = None,
- operation_id: Optional[str] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- include_in_schema: bool = True,
- response_class: Union[type[Response], DefaultPlaceholder] = Default(
- JSONResponse
- ),
- name: Optional[str] = None,
- route_class_override: Optional[type[APIRoute]] = None,
- callbacks: Optional[list[BaseRoute]] = None,
- openapi_extra: Optional[dict[str, Any]] = None,
- generate_unique_id_function: Union[
- Callable[[APIRoute], str], DefaultPlaceholder
- ] = Default(generate_unique_id),
- ) -> None:
- route_class = route_class_override or self.route_class
- responses = responses or {}
- combined_responses = {**self.responses, **responses}
- current_response_class = get_value_or_default(
- response_class, self.default_response_class
- )
- current_tags = self.tags.copy()
- if tags:
- current_tags.extend(tags)
- current_dependencies = self.dependencies.copy()
- if dependencies:
- current_dependencies.extend(dependencies)
- current_callbacks = self.callbacks.copy()
- if callbacks:
- current_callbacks.extend(callbacks)
- current_generate_unique_id = get_value_or_default(
- generate_unique_id_function, self.generate_unique_id_function
- )
- route = route_class(
- self.prefix + path,
- endpoint=endpoint,
- response_model=response_model,
- status_code=status_code,
- tags=current_tags,
- dependencies=current_dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=combined_responses,
- deprecated=deprecated or self.deprecated,
- methods=methods,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema and self.include_in_schema,
- response_class=current_response_class,
- name=name,
- dependency_overrides_provider=self.dependency_overrides_provider,
- callbacks=current_callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=current_generate_unique_id,
- )
- self.routes.append(route)
-
- def api_route(
- self,
- path: str,
- *,
- response_model: Any = Default(None),
- status_code: Optional[int] = None,
- tags: Optional[list[Union[str, Enum]]] = None,
- dependencies: Optional[Sequence[params.Depends]] = None,
- summary: Optional[str] = None,
- description: Optional[str] = None,
- response_description: str = "Successful Response",
- responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
- deprecated: Optional[bool] = None,
- methods: Optional[list[str]] = None,
- operation_id: Optional[str] = None,
- response_model_include: Optional[IncEx] = None,
- response_model_exclude: Optional[IncEx] = None,
- response_model_by_alias: bool = True,
- response_model_exclude_unset: bool = False,
- response_model_exclude_defaults: bool = False,
- response_model_exclude_none: bool = False,
- include_in_schema: bool = True,
- response_class: type[Response] = Default(JSONResponse),
- name: Optional[str] = None,
- callbacks: Optional[list[BaseRoute]] = None,
- openapi_extra: Optional[dict[str, Any]] = None,
- generate_unique_id_function: Callable[[APIRoute], str] = Default(
- generate_unique_id
- ),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_api_route(
- path,
- func,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=methods,
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
- return func
-
- return decorator
-
- def add_api_websocket_route(
- self,
- path: str,
- endpoint: Callable[..., Any],
- name: Optional[str] = None,
- *,
- dependencies: Optional[Sequence[params.Depends]] = None,
- ) -> None:
- current_dependencies = self.dependencies.copy()
- if dependencies:
- current_dependencies.extend(dependencies)
-
- route = APIWebSocketRoute(
- self.prefix + path,
- endpoint=endpoint,
- name=name,
- dependencies=current_dependencies,
- dependency_overrides_provider=self.dependency_overrides_provider,
- )
- self.routes.append(route)
-
- def websocket(
- self,
- path: Annotated[
- str,
- Doc(
- """
- WebSocket path.
- """
- ),
- ],
- name: Annotated[
- Optional[str],
- Doc(
- """
- A name for the WebSocket. Only used internally.
- """
- ),
- ] = None,
- *,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be used for this
- WebSocket.
-
- Read more about it in the
- [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
- """
- ),
- ] = None,
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Decorate a WebSocket function.
-
- Read more about it in the
- [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
-
- **Example**
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI, WebSocket
-
- app = FastAPI()
- router = APIRouter()
-
- @router.websocket("/ws")
- async def websocket_endpoint(websocket: WebSocket):
- await websocket.accept()
- while True:
- data = await websocket.receive_text()
- await websocket.send_text(f"Message text was: {data}")
-
- app.include_router(router)
- ```
- """
-
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_api_websocket_route(
- path, func, name=name, dependencies=dependencies
- )
- return func
-
- return decorator
-
- def websocket_route(
- self, path: str, name: Union[str, None] = None
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_websocket_route(path, func, name=name)
- return func
-
- return decorator
-
- def include_router(
- self,
- router: Annotated["APIRouter", Doc("The `APIRouter` to include.")],
- *,
- prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "",
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to all the *path operations* in this
- router.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to all the
- *path operations* in this router.
-
- Read more about it in the
- [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- default_response_class: Annotated[
- type[Response],
- Doc(
- """
- The default response class to be used.
-
- Read more in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
- """
- ),
- ] = Default(JSONResponse),
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses to be shown in OpenAPI.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
-
- And in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- OpenAPI callbacks that should apply to all *path operations* in this
- router.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark all *path operations* in this router as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include (or not) all the *path operations* in this router in the
- generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = True,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> None:
- """
- Include another `APIRouter` in the same current `APIRouter`.
-
- Read more about it in the
- [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
-
- app = FastAPI()
- internal_router = APIRouter()
- users_router = APIRouter()
-
- @users_router.get("/users/")
- def read_users():
- return [{"name": "Rick"}, {"name": "Morty"}]
-
- internal_router.include_router(users_router)
- app.include_router(internal_router)
- ```
- """
- if prefix:
- assert prefix.startswith("/"), "A path prefix must start with '/'"
- assert not prefix.endswith("/"), (
- "A path prefix must not end with '/', as the routes will start with '/'"
- )
- else:
- for r in router.routes:
- path = getattr(r, "path") # noqa: B009
- name = getattr(r, "name", "unknown")
- if path is not None and not path:
- raise FastAPIError(
- f"Prefix and path cannot be both empty (path operation: {name})"
- )
- if responses is None:
- responses = {}
- for route in router.routes:
- if isinstance(route, APIRoute):
- combined_responses = {**responses, **route.responses}
- use_response_class = get_value_or_default(
- route.response_class,
- router.default_response_class,
- default_response_class,
- self.default_response_class,
- )
- current_tags = []
- if tags:
- current_tags.extend(tags)
- if route.tags:
- current_tags.extend(route.tags)
- current_dependencies: list[params.Depends] = []
- if dependencies:
- current_dependencies.extend(dependencies)
- if route.dependencies:
- current_dependencies.extend(route.dependencies)
- current_callbacks = []
- if callbacks:
- current_callbacks.extend(callbacks)
- if route.callbacks:
- current_callbacks.extend(route.callbacks)
- current_generate_unique_id = get_value_or_default(
- route.generate_unique_id_function,
- router.generate_unique_id_function,
- generate_unique_id_function,
- self.generate_unique_id_function,
- )
- self.add_api_route(
- prefix + route.path,
- route.endpoint,
- response_model=route.response_model,
- status_code=route.status_code,
- tags=current_tags,
- dependencies=current_dependencies,
- summary=route.summary,
- description=route.description,
- response_description=route.response_description,
- responses=combined_responses,
- deprecated=route.deprecated or deprecated or self.deprecated,
- methods=route.methods,
- operation_id=route.operation_id,
- response_model_include=route.response_model_include,
- response_model_exclude=route.response_model_exclude,
- response_model_by_alias=route.response_model_by_alias,
- response_model_exclude_unset=route.response_model_exclude_unset,
- response_model_exclude_defaults=route.response_model_exclude_defaults,
- response_model_exclude_none=route.response_model_exclude_none,
- include_in_schema=route.include_in_schema
- and self.include_in_schema
- and include_in_schema,
- response_class=use_response_class,
- name=route.name,
- route_class_override=type(route),
- callbacks=current_callbacks,
- openapi_extra=route.openapi_extra,
- generate_unique_id_function=current_generate_unique_id,
- )
- elif isinstance(route, routing.Route):
- methods = list(route.methods or [])
- self.add_route(
- prefix + route.path,
- route.endpoint,
- methods=methods,
- include_in_schema=route.include_in_schema,
- name=route.name,
- )
- elif isinstance(route, APIWebSocketRoute):
- current_dependencies = []
- if dependencies:
- current_dependencies.extend(dependencies)
- if route.dependencies:
- current_dependencies.extend(route.dependencies)
- self.add_api_websocket_route(
- prefix + route.path,
- route.endpoint,
- dependencies=current_dependencies,
- name=route.name,
- )
- elif isinstance(route, routing.WebSocketRoute):
- self.add_websocket_route(
- prefix + route.path, route.endpoint, name=route.name
- )
- for handler in router.on_startup:
- self.add_event_handler("startup", handler)
- for handler in router.on_shutdown:
- self.add_event_handler("shutdown", handler)
- self.lifespan_context = _merge_lifespan_context(
- self.lifespan_context,
- router.lifespan_context,
- )
-
- def get(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP GET operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
-
- app = FastAPI()
- router = APIRouter()
-
- @router.get("/items/")
- def read_items():
- return [{"name": "Empanada"}, {"name": "Arepa"}]
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["GET"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def put(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP PUT operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
- router = APIRouter()
-
- @router.put("/items/{item_id}")
- def replace_item(item_id: str, item: Item):
- return {"message": "Item replaced", "id": item_id}
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["PUT"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def post(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP POST operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
- router = APIRouter()
-
- @router.post("/items/")
- def create_item(item: Item):
- return {"message": "Item created"}
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["POST"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def delete(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP DELETE operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
-
- app = FastAPI()
- router = APIRouter()
-
- @router.delete("/items/{item_id}")
- def delete_item(item_id: str):
- return {"message": "Item deleted"}
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["DELETE"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def options(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP OPTIONS operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
-
- app = FastAPI()
- router = APIRouter()
-
- @router.options("/items/")
- def get_item_options():
- return {"additions": ["Aji", "Guacamole"]}
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["OPTIONS"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def head(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP HEAD operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
- router = APIRouter()
-
- @router.head("/items/", status_code=204)
- def get_items_headers(response: Response):
- response.headers["X-Cat-Dog"] = "Alone in the world"
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["HEAD"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def patch(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP PATCH operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
- router = APIRouter()
-
- @router.patch("/items/")
- def update_item(item: Item):
- return {"message": "Item updated in place"}
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["PATCH"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- def trace(
- self,
- path: Annotated[
- str,
- Doc(
- """
- The URL path to be used for this *path operation*.
-
- For example, in `http://example.com/items`, the path is `/items`.
- """
- ),
- ],
- *,
- response_model: Annotated[
- Any,
- Doc(
- """
- The type to use for the response.
-
- It could be any valid Pydantic *field* type. So, it doesn't have to
- be a Pydantic model, it could be other things, like a `list`, `dict`,
- etc.
-
- It will be used for:
-
- * Documentation: the generated OpenAPI (and the UI at `/docs`) will
- show it as the response (JSON Schema).
- * Serialization: you could return an arbitrary object and the
- `response_model` would be used to serialize that object into the
- corresponding JSON.
- * Filtering: the JSON sent to the client will only contain the data
- (fields) defined in the `response_model`. If you returned an object
- that contains an attribute `password` but the `response_model` does
- not include that field, the JSON sent to the client would not have
- that `password`.
- * Validation: whatever you return will be serialized with the
- `response_model`, converting any data as necessary to generate the
- corresponding JSON. But if the data in the object returned is not
- valid, that would mean a violation of the contract with the client,
- so it's an error from the API developer. So, FastAPI will raise an
- error and return a 500 error code (Internal Server Error).
-
- Read more about it in the
- [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
- """
- ),
- ] = Default(None),
- status_code: Annotated[
- Optional[int],
- Doc(
- """
- The default status code to be used for the response.
-
- You could override the status code by returning a response directly.
-
- Read more about it in the
- [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).
- """
- ),
- ] = None,
- tags: Annotated[
- Optional[list[Union[str, Enum]]],
- Doc(
- """
- A list of tags to be applied to the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).
- """
- ),
- ] = None,
- dependencies: Annotated[
- Optional[Sequence[params.Depends]],
- Doc(
- """
- A list of dependencies (using `Depends()`) to be applied to the
- *path operation*.
-
- Read more about it in the
- [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
- """
- ),
- ] = None,
- summary: Annotated[
- Optional[str],
- Doc(
- """
- A summary for the *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- A description for the *path operation*.
-
- If not provided, it will be extracted automatically from the docstring
- of the *path operation function*.
-
- It can contain Markdown.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
- """
- ),
- ] = None,
- response_description: Annotated[
- str,
- Doc(
- """
- The description for the default response.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = "Successful Response",
- responses: Annotated[
- Optional[dict[Union[int, str], dict[str, Any]]],
- Doc(
- """
- Additional responses that could be returned by this *path operation*.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- deprecated: Annotated[
- Optional[bool],
- Doc(
- """
- Mark this *path operation* as deprecated.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- operation_id: Annotated[
- Optional[str],
- Doc(
- """
- Custom operation ID to be used by this *path operation*.
-
- By default, it is generated automatically.
-
- If you provide a custom operation ID, you need to make sure it is
- unique for the whole API.
-
- You can customize the
- operation ID generation with the parameter
- `generate_unique_id_function` in the `FastAPI` class.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = None,
- response_model_include: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to include only certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_exclude: Annotated[
- Optional[IncEx],
- Doc(
- """
- Configuration passed to Pydantic to exclude certain fields in the
- response data.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = None,
- response_model_by_alias: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response model
- should be serialized by alias when an alias is used.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
- """
- ),
- ] = True,
- response_model_exclude_unset: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that were not set and
- have their default values. This is different from
- `response_model_exclude_defaults` in that if the fields are set,
- they will be included in the response, even if the value is the same
- as the default.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_defaults: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data
- should have all the fields, including the ones that have the same value
- as the default. This is different from `response_model_exclude_unset`
- in that if the fields are set but contain the same default values,
- they will be excluded from the response.
-
- When `True`, default values are omitted from the response.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter).
- """
- ),
- ] = False,
- response_model_exclude_none: Annotated[
- bool,
- Doc(
- """
- Configuration passed to Pydantic to define if the response data should
- exclude fields set to `None`.
-
- This is much simpler (less smart) than `response_model_exclude_unset`
- and `response_model_exclude_defaults`. You probably want to use one of
- those two instead of this one, as those allow returning `None` values
- when it makes sense.
-
- Read more about it in the
- [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none).
- """
- ),
- ] = False,
- include_in_schema: Annotated[
- bool,
- Doc(
- """
- Include this *path operation* in the generated OpenAPI schema.
-
- This affects the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
- """
- ),
- ] = True,
- response_class: Annotated[
- type[Response],
- Doc(
- """
- Response class to be used for this *path operation*.
-
- This will not be used if you return a response directly.
-
- Read more about it in the
- [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse).
- """
- ),
- ] = Default(JSONResponse),
- name: Annotated[
- Optional[str],
- Doc(
- """
- Name for this *path operation*. Only used internally.
- """
- ),
- ] = None,
- callbacks: Annotated[
- Optional[list[BaseRoute]],
- Doc(
- """
- List of *path operations* that will be used as OpenAPI callbacks.
-
- This is only for OpenAPI documentation, the callbacks won't be used
- directly.
-
- It will be added to the generated OpenAPI (e.g. visible at `/docs`).
-
- Read more about it in the
- [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
- """
- ),
- ] = None,
- openapi_extra: Annotated[
- Optional[dict[str, Any]],
- Doc(
- """
- Extra metadata to be included in the OpenAPI schema for this *path
- operation*.
-
- Read more about it in the
- [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema).
- """
- ),
- ] = None,
- generate_unique_id_function: Annotated[
- Callable[[APIRoute], str],
- Doc(
- """
- Customize the function used to generate unique IDs for the *path
- operations* shown in the generated OpenAPI.
-
- This is particularly useful when automatically generating clients or
- SDKs for your API.
-
- Read more about it in the
- [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
- """
- ),
- ] = Default(generate_unique_id),
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add a *path operation* using an HTTP TRACE operation.
-
- ## Example
-
- ```python
- from fastapi import APIRouter, FastAPI
- from pydantic import BaseModel
-
- class Item(BaseModel):
- name: str
- description: str | None = None
-
- app = FastAPI()
- router = APIRouter()
-
- @router.trace("/items/{item_id}")
- def trace_item(item_id: str):
- return None
-
- app.include_router(router)
- ```
- """
- return self.api_route(
- path=path,
- response_model=response_model,
- status_code=status_code,
- tags=tags,
- dependencies=dependencies,
- summary=summary,
- description=description,
- response_description=response_description,
- responses=responses,
- deprecated=deprecated,
- methods=["TRACE"],
- operation_id=operation_id,
- response_model_include=response_model_include,
- response_model_exclude=response_model_exclude,
- response_model_by_alias=response_model_by_alias,
- response_model_exclude_unset=response_model_exclude_unset,
- response_model_exclude_defaults=response_model_exclude_defaults,
- response_model_exclude_none=response_model_exclude_none,
- include_in_schema=include_in_schema,
- response_class=response_class,
- name=name,
- callbacks=callbacks,
- openapi_extra=openapi_extra,
- generate_unique_id_function=generate_unique_id_function,
- )
-
- @deprecated(
- """
- on_event is deprecated, use lifespan event handlers instead.
-
- Read more about it in the
- [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
- """
- )
- def on_event(
- self,
- event_type: Annotated[
- str,
- Doc(
- """
- The type of event. `startup` or `shutdown`.
- """
- ),
- ],
- ) -> Callable[[DecoratedCallable], DecoratedCallable]:
- """
- Add an event handler for the router.
-
- `on_event` is deprecated, use `lifespan` event handlers instead.
-
- Read more about it in the
- [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated).
- """
-
- def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_event_handler(event_type, func)
- return func
-
- return decorator
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__init__.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__init__.py
deleted file mode 100644
index 3aa6bf2..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from .api_key import APIKeyCookie as APIKeyCookie
-from .api_key import APIKeyHeader as APIKeyHeader
-from .api_key import APIKeyQuery as APIKeyQuery
-from .http import HTTPAuthorizationCredentials as HTTPAuthorizationCredentials
-from .http import HTTPBasic as HTTPBasic
-from .http import HTTPBasicCredentials as HTTPBasicCredentials
-from .http import HTTPBearer as HTTPBearer
-from .http import HTTPDigest as HTTPDigest
-from .oauth2 import OAuth2 as OAuth2
-from .oauth2 import OAuth2AuthorizationCodeBearer as OAuth2AuthorizationCodeBearer
-from .oauth2 import OAuth2PasswordBearer as OAuth2PasswordBearer
-from .oauth2 import OAuth2PasswordRequestForm as OAuth2PasswordRequestForm
-from .oauth2 import OAuth2PasswordRequestFormStrict as OAuth2PasswordRequestFormStrict
-from .oauth2 import SecurityScopes as SecurityScopes
-from .open_id_connect_url import OpenIdConnect as OpenIdConnect
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 93bbb9d..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc
deleted file mode 100644
index 2692c85..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index c575ee4..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc
deleted file mode 100644
index 1384c2e..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc
deleted file mode 100644
index b1f51dc..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc
deleted file mode 100644
index 76960f3..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc
deleted file mode 100644
index 47f5f57..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/api_key.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/api_key.py
deleted file mode 100644
index 18dfb8e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/api_key.py
+++ /dev/null
@@ -1,318 +0,0 @@
-from typing import Annotated, Optional, Union
-
-from annotated_doc import Doc
-from fastapi.openapi.models import APIKey, APIKeyIn
-from fastapi.security.base import SecurityBase
-from starlette.exceptions import HTTPException
-from starlette.requests import Request
-from starlette.status import HTTP_401_UNAUTHORIZED
-
-
-class APIKeyBase(SecurityBase):
- def __init__(
- self,
- location: APIKeyIn,
- name: str,
- description: Union[str, None],
- scheme_name: Union[str, None],
- auto_error: bool,
- ):
- self.auto_error = auto_error
-
- self.model: APIKey = APIKey(
- **{"in": location},
- name=name,
- description=description,
- )
- self.scheme_name = scheme_name or self.__class__.__name__
-
- def make_not_authenticated_error(self) -> HTTPException:
- """
- The WWW-Authenticate header is not standardized for API Key authentication but
- the HTTP specification requires that an error of 401 "Unauthorized" must
- include a WWW-Authenticate header.
-
- Ref: https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized
-
- For this, this method sends a custom challenge `APIKey`.
- """
- return HTTPException(
- status_code=HTTP_401_UNAUTHORIZED,
- detail="Not authenticated",
- headers={"WWW-Authenticate": "APIKey"},
- )
-
- def check_api_key(self, api_key: Optional[str]) -> Optional[str]:
- if not api_key:
- if self.auto_error:
- raise self.make_not_authenticated_error()
- return None
- return api_key
-
-
-class APIKeyQuery(APIKeyBase):
- """
- API key authentication using a query parameter.
-
- This defines the name of the query parameter that should be provided in the request
- with the API key and integrates that into the OpenAPI documentation. It extracts
- the key value sent in the query parameter automatically and provides it as the
- dependency result. But it doesn't define how to send that API key to the client.
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be a string containing the key value.
-
- ## Example
-
- ```python
- from fastapi import Depends, FastAPI
- from fastapi.security import APIKeyQuery
-
- app = FastAPI()
-
- query_scheme = APIKeyQuery(name="api_key")
-
-
- @app.get("/items/")
- async def read_items(api_key: str = Depends(query_scheme)):
- return {"api_key": api_key}
- ```
- """
-
- def __init__(
- self,
- *,
- name: Annotated[
- str,
- Doc("Query parameter name."),
- ],
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the query parameter is not provided, `APIKeyQuery` will
- automatically cancel the request and send the client an error.
-
- If `auto_error` is set to `False`, when the query parameter is not
- available, instead of erroring out, the dependency result will be
- `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in a query
- parameter or in an HTTP Bearer token).
- """
- ),
- ] = True,
- ):
- super().__init__(
- location=APIKeyIn.query,
- name=name,
- scheme_name=scheme_name,
- description=description,
- auto_error=auto_error,
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- api_key = request.query_params.get(self.model.name)
- return self.check_api_key(api_key)
-
-
-class APIKeyHeader(APIKeyBase):
- """
- API key authentication using a header.
-
- This defines the name of the header that should be provided in the request with
- the API key and integrates that into the OpenAPI documentation. It extracts
- the key value sent in the header automatically and provides it as the dependency
- result. But it doesn't define how to send that key to the client.
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be a string containing the key value.
-
- ## Example
-
- ```python
- from fastapi import Depends, FastAPI
- from fastapi.security import APIKeyHeader
-
- app = FastAPI()
-
- header_scheme = APIKeyHeader(name="x-key")
-
-
- @app.get("/items/")
- async def read_items(key: str = Depends(header_scheme)):
- return {"key": key}
- ```
- """
-
- def __init__(
- self,
- *,
- name: Annotated[str, Doc("Header name.")],
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the header is not provided, `APIKeyHeader` will
- automatically cancel the request and send the client an error.
-
- If `auto_error` is set to `False`, when the header is not available,
- instead of erroring out, the dependency result will be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in a header or
- in an HTTP Bearer token).
- """
- ),
- ] = True,
- ):
- super().__init__(
- location=APIKeyIn.header,
- name=name,
- scheme_name=scheme_name,
- description=description,
- auto_error=auto_error,
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- api_key = request.headers.get(self.model.name)
- return self.check_api_key(api_key)
-
-
-class APIKeyCookie(APIKeyBase):
- """
- API key authentication using a cookie.
-
- This defines the name of the cookie that should be provided in the request with
- the API key and integrates that into the OpenAPI documentation. It extracts
- the key value sent in the cookie automatically and provides it as the dependency
- result. But it doesn't define how to set that cookie.
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be a string containing the key value.
-
- ## Example
-
- ```python
- from fastapi import Depends, FastAPI
- from fastapi.security import APIKeyCookie
-
- app = FastAPI()
-
- cookie_scheme = APIKeyCookie(name="session")
-
-
- @app.get("/items/")
- async def read_items(session: str = Depends(cookie_scheme)):
- return {"session": session}
- ```
- """
-
- def __init__(
- self,
- *,
- name: Annotated[str, Doc("Cookie name.")],
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the cookie is not provided, `APIKeyCookie` will
- automatically cancel the request and send the client an error.
-
- If `auto_error` is set to `False`, when the cookie is not available,
- instead of erroring out, the dependency result will be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in a cookie or
- in an HTTP Bearer token).
- """
- ),
- ] = True,
- ):
- super().__init__(
- location=APIKeyIn.cookie,
- name=name,
- scheme_name=scheme_name,
- description=description,
- auto_error=auto_error,
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- api_key = request.cookies.get(self.model.name)
- return self.check_api_key(api_key)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/base.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/base.py
deleted file mode 100644
index c43555d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/base.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from fastapi.openapi.models import SecurityBase as SecurityBaseModel
-
-
-class SecurityBase:
- model: SecurityBaseModel
- scheme_name: str
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/http.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/http.py
deleted file mode 100644
index b4c3bc6..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/http.py
+++ /dev/null
@@ -1,423 +0,0 @@
-import binascii
-from base64 import b64decode
-from typing import Annotated, Optional
-
-from annotated_doc import Doc
-from fastapi.exceptions import HTTPException
-from fastapi.openapi.models import HTTPBase as HTTPBaseModel
-from fastapi.openapi.models import HTTPBearer as HTTPBearerModel
-from fastapi.security.base import SecurityBase
-from fastapi.security.utils import get_authorization_scheme_param
-from pydantic import BaseModel
-from starlette.requests import Request
-from starlette.status import HTTP_401_UNAUTHORIZED
-
-
-class HTTPBasicCredentials(BaseModel):
- """
- The HTTP Basic credentials given as the result of using `HTTPBasic` in a
- dependency.
-
- Read more about it in the
- [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/).
- """
-
- username: Annotated[str, Doc("The HTTP Basic username.")]
- password: Annotated[str, Doc("The HTTP Basic password.")]
-
-
-class HTTPAuthorizationCredentials(BaseModel):
- """
- The HTTP authorization credentials in the result of using `HTTPBearer` or
- `HTTPDigest` in a dependency.
-
- The HTTP authorization header value is split by the first space.
-
- The first part is the `scheme`, the second part is the `credentials`.
-
- For example, in an HTTP Bearer token scheme, the client will send a header
- like:
-
- ```
- Authorization: Bearer deadbeef12346
- ```
-
- In this case:
-
- * `scheme` will have the value `"Bearer"`
- * `credentials` will have the value `"deadbeef12346"`
- """
-
- scheme: Annotated[
- str,
- Doc(
- """
- The HTTP authorization scheme extracted from the header value.
- """
- ),
- ]
- credentials: Annotated[
- str,
- Doc(
- """
- The HTTP authorization credentials extracted from the header value.
- """
- ),
- ]
-
-
-class HTTPBase(SecurityBase):
- def __init__(
- self,
- *,
- scheme: str,
- scheme_name: Optional[str] = None,
- description: Optional[str] = None,
- auto_error: bool = True,
- ):
- self.model: HTTPBaseModel = HTTPBaseModel(
- scheme=scheme, description=description
- )
- self.scheme_name = scheme_name or self.__class__.__name__
- self.auto_error = auto_error
-
- def make_authenticate_headers(self) -> dict[str, str]:
- return {"WWW-Authenticate": f"{self.model.scheme.title()}"}
-
- def make_not_authenticated_error(self) -> HTTPException:
- return HTTPException(
- status_code=HTTP_401_UNAUTHORIZED,
- detail="Not authenticated",
- headers=self.make_authenticate_headers(),
- )
-
- async def __call__(
- self, request: Request
- ) -> Optional[HTTPAuthorizationCredentials]:
- authorization = request.headers.get("Authorization")
- scheme, credentials = get_authorization_scheme_param(authorization)
- if not (authorization and scheme and credentials):
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
-
-
-class HTTPBasic(HTTPBase):
- """
- HTTP Basic authentication.
-
- Ref: https://datatracker.ietf.org/doc/html/rfc7617
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be an `HTTPBasicCredentials` object containing the
- `username` and the `password`.
-
- Read more about it in the
- [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/).
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
- from fastapi.security import HTTPBasic, HTTPBasicCredentials
-
- app = FastAPI()
-
- security = HTTPBasic()
-
-
- @app.get("/users/me")
- def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
- return {"username": credentials.username, "password": credentials.password}
- ```
- """
-
- def __init__(
- self,
- *,
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- realm: Annotated[
- Optional[str],
- Doc(
- """
- HTTP Basic authentication realm.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the HTTP Basic authentication is not provided (a
- header), `HTTPBasic` will automatically cancel the request and send the
- client an error.
-
- If `auto_error` is set to `False`, when the HTTP Basic authentication
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in HTTP Basic
- authentication or in an HTTP Bearer token).
- """
- ),
- ] = True,
- ):
- self.model = HTTPBaseModel(scheme="basic", description=description)
- self.scheme_name = scheme_name or self.__class__.__name__
- self.realm = realm
- self.auto_error = auto_error
-
- def make_authenticate_headers(self) -> dict[str, str]:
- if self.realm:
- return {"WWW-Authenticate": f'Basic realm="{self.realm}"'}
- return {"WWW-Authenticate": "Basic"}
-
- async def __call__( # type: ignore
- self, request: Request
- ) -> Optional[HTTPBasicCredentials]:
- authorization = request.headers.get("Authorization")
- scheme, param = get_authorization_scheme_param(authorization)
- if not authorization or scheme.lower() != "basic":
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- try:
- data = b64decode(param).decode("ascii")
- except (ValueError, UnicodeDecodeError, binascii.Error) as e:
- raise self.make_not_authenticated_error() from e
- username, separator, password = data.partition(":")
- if not separator:
- raise self.make_not_authenticated_error()
- return HTTPBasicCredentials(username=username, password=password)
-
-
-class HTTPBearer(HTTPBase):
- """
- HTTP Bearer token authentication.
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be an `HTTPAuthorizationCredentials` object containing
- the `scheme` and the `credentials`.
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
- from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
-
- app = FastAPI()
-
- security = HTTPBearer()
-
-
- @app.get("/users/me")
- def read_current_user(
- credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
- ):
- return {"scheme": credentials.scheme, "credentials": credentials.credentials}
- ```
- """
-
- def __init__(
- self,
- *,
- bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None,
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the HTTP Bearer token is not provided (in an
- `Authorization` header), `HTTPBearer` will automatically cancel the
- request and send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Bearer token
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in an HTTP
- Bearer token or in a cookie).
- """
- ),
- ] = True,
- ):
- self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description)
- self.scheme_name = scheme_name or self.__class__.__name__
- self.auto_error = auto_error
-
- async def __call__(
- self, request: Request
- ) -> Optional[HTTPAuthorizationCredentials]:
- authorization = request.headers.get("Authorization")
- scheme, credentials = get_authorization_scheme_param(authorization)
- if not (authorization and scheme and credentials):
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- if scheme.lower() != "bearer":
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
-
-
-class HTTPDigest(HTTPBase):
- """
- HTTP Digest authentication.
-
- **Warning**: this is only a stub to connect the components with OpenAPI in FastAPI,
- but it doesn't implement the full Digest scheme, you would need to to subclass it
- and implement it in your code.
-
- Ref: https://datatracker.ietf.org/doc/html/rfc7616
-
- ## Usage
-
- Create an instance object and use that object as the dependency in `Depends()`.
-
- The dependency result will be an `HTTPAuthorizationCredentials` object containing
- the `scheme` and the `credentials`.
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
- from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest
-
- app = FastAPI()
-
- security = HTTPDigest()
-
-
- @app.get("/users/me")
- def read_current_user(
- credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
- ):
- return {"scheme": credentials.scheme, "credentials": credentials.credentials}
- ```
- """
-
- def __init__(
- self,
- *,
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if the HTTP Digest is not provided, `HTTPDigest` will
- automatically cancel the request and send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Digest is not
- available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, in HTTP
- Digest or in a cookie).
- """
- ),
- ] = True,
- ):
- self.model = HTTPBaseModel(scheme="digest", description=description)
- self.scheme_name = scheme_name or self.__class__.__name__
- self.auto_error = auto_error
-
- async def __call__(
- self, request: Request
- ) -> Optional[HTTPAuthorizationCredentials]:
- authorization = request.headers.get("Authorization")
- scheme, credentials = get_authorization_scheme_param(authorization)
- if not (authorization and scheme and credentials):
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- if scheme.lower() != "digest":
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/oauth2.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/oauth2.py
deleted file mode 100644
index fc49ba1..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/oauth2.py
+++ /dev/null
@@ -1,663 +0,0 @@
-from typing import Annotated, Any, Optional, Union, cast
-
-from annotated_doc import Doc
-from fastapi.exceptions import HTTPException
-from fastapi.openapi.models import OAuth2 as OAuth2Model
-from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
-from fastapi.param_functions import Form
-from fastapi.security.base import SecurityBase
-from fastapi.security.utils import get_authorization_scheme_param
-from starlette.requests import Request
-from starlette.status import HTTP_401_UNAUTHORIZED
-
-
-class OAuth2PasswordRequestForm:
- """
- This is a dependency class to collect the `username` and `password` as form data
- for an OAuth2 password flow.
-
- The OAuth2 specification dictates that for a password flow the data should be
- collected using form data (instead of JSON) and that it should have the specific
- fields `username` and `password`.
-
- All the initialization parameters are extracted from the request.
-
- Read more about it in the
- [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
- from fastapi.security import OAuth2PasswordRequestForm
-
- app = FastAPI()
-
-
- @app.post("/login")
- def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
- data = {}
- data["scopes"] = []
- for scope in form_data.scopes:
- data["scopes"].append(scope)
- if form_data.client_id:
- data["client_id"] = form_data.client_id
- if form_data.client_secret:
- data["client_secret"] = form_data.client_secret
- return data
- ```
-
- Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
- You could have custom internal logic to separate it by colon characters (`:`) or
- similar, and get the two parts `items` and `read`. Many applications do that to
- group and organize permissions, you could do it as well in your application, just
- know that that it is application specific, it's not part of the specification.
- """
-
- def __init__(
- self,
- *,
- grant_type: Annotated[
- Union[str, None],
- Form(pattern="^password$"),
- Doc(
- """
- The OAuth2 spec says it is required and MUST be the fixed string
- "password". Nevertheless, this dependency class is permissive and
- allows not passing it. If you want to enforce it, use instead the
- `OAuth2PasswordRequestFormStrict` dependency.
- """
- ),
- ] = None,
- username: Annotated[
- str,
- Form(),
- Doc(
- """
- `username` string. The OAuth2 spec requires the exact field name
- `username`.
- """
- ),
- ],
- password: Annotated[
- str,
- Form(json_schema_extra={"format": "password"}),
- Doc(
- """
- `password` string. The OAuth2 spec requires the exact field name
- `password`.
- """
- ),
- ],
- scope: Annotated[
- str,
- Form(),
- Doc(
- """
- A single string with actually several scopes separated by spaces. Each
- scope is also a string.
-
- For example, a single string with:
-
- ```python
- "items:read items:write users:read profile openid"
- ````
-
- would represent the scopes:
-
- * `items:read`
- * `items:write`
- * `users:read`
- * `profile`
- * `openid`
- """
- ),
- ] = "",
- client_id: Annotated[
- Union[str, None],
- Form(),
- Doc(
- """
- If there's a `client_id`, it can be sent as part of the form fields.
- But the OAuth2 specification recommends sending the `client_id` and
- `client_secret` (if any) using HTTP Basic auth.
- """
- ),
- ] = None,
- client_secret: Annotated[
- Union[str, None],
- Form(json_schema_extra={"format": "password"}),
- Doc(
- """
- If there's a `client_password` (and a `client_id`), they can be sent
- as part of the form fields. But the OAuth2 specification recommends
- sending the `client_id` and `client_secret` (if any) using HTTP Basic
- auth.
- """
- ),
- ] = None,
- ):
- self.grant_type = grant_type
- self.username = username
- self.password = password
- self.scopes = scope.split()
- self.client_id = client_id
- self.client_secret = client_secret
-
-
-class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
- """
- This is a dependency class to collect the `username` and `password` as form data
- for an OAuth2 password flow.
-
- The OAuth2 specification dictates that for a password flow the data should be
- collected using form data (instead of JSON) and that it should have the specific
- fields `username` and `password`.
-
- All the initialization parameters are extracted from the request.
-
- The only difference between `OAuth2PasswordRequestFormStrict` and
- `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the
- client to send the form field `grant_type` with the value `"password"`, which
- is required in the OAuth2 specification (it seems that for no particular reason),
- while for `OAuth2PasswordRequestForm` `grant_type` is optional.
-
- Read more about it in the
- [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
-
- ## Example
-
- ```python
- from typing import Annotated
-
- from fastapi import Depends, FastAPI
- from fastapi.security import OAuth2PasswordRequestForm
-
- app = FastAPI()
-
-
- @app.post("/login")
- def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):
- data = {}
- data["scopes"] = []
- for scope in form_data.scopes:
- data["scopes"].append(scope)
- if form_data.client_id:
- data["client_id"] = form_data.client_id
- if form_data.client_secret:
- data["client_secret"] = form_data.client_secret
- return data
- ```
-
- Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
- You could have custom internal logic to separate it by colon characters (`:`) or
- similar, and get the two parts `items` and `read`. Many applications do that to
- group and organize permissions, you could do it as well in your application, just
- know that that it is application specific, it's not part of the specification.
-
-
- grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
- This dependency is strict about it. If you want to be permissive, use instead the
- OAuth2PasswordRequestForm dependency class.
- username: username string. The OAuth2 spec requires the exact field name "username".
- password: password string. The OAuth2 spec requires the exact field name "password".
- scope: Optional string. Several scopes (each one a string) separated by spaces. E.g.
- "items:read items:write users:read profile openid"
- client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
- using HTTP Basic auth, as: client_id:client_secret
- client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
- using HTTP Basic auth, as: client_id:client_secret
- """
-
- def __init__(
- self,
- grant_type: Annotated[
- str,
- Form(pattern="^password$"),
- Doc(
- """
- The OAuth2 spec says it is required and MUST be the fixed string
- "password". This dependency is strict about it. If you want to be
- permissive, use instead the `OAuth2PasswordRequestForm` dependency
- class.
- """
- ),
- ],
- username: Annotated[
- str,
- Form(),
- Doc(
- """
- `username` string. The OAuth2 spec requires the exact field name
- `username`.
- """
- ),
- ],
- password: Annotated[
- str,
- Form(),
- Doc(
- """
- `password` string. The OAuth2 spec requires the exact field name
- `password`.
- """
- ),
- ],
- scope: Annotated[
- str,
- Form(),
- Doc(
- """
- A single string with actually several scopes separated by spaces. Each
- scope is also a string.
-
- For example, a single string with:
-
- ```python
- "items:read items:write users:read profile openid"
- ````
-
- would represent the scopes:
-
- * `items:read`
- * `items:write`
- * `users:read`
- * `profile`
- * `openid`
- """
- ),
- ] = "",
- client_id: Annotated[
- Union[str, None],
- Form(),
- Doc(
- """
- If there's a `client_id`, it can be sent as part of the form fields.
- But the OAuth2 specification recommends sending the `client_id` and
- `client_secret` (if any) using HTTP Basic auth.
- """
- ),
- ] = None,
- client_secret: Annotated[
- Union[str, None],
- Form(),
- Doc(
- """
- If there's a `client_password` (and a `client_id`), they can be sent
- as part of the form fields. But the OAuth2 specification recommends
- sending the `client_id` and `client_secret` (if any) using HTTP Basic
- auth.
- """
- ),
- ] = None,
- ):
- super().__init__(
- grant_type=grant_type,
- username=username,
- password=password,
- scope=scope,
- client_id=client_id,
- client_secret=client_secret,
- )
-
-
-class OAuth2(SecurityBase):
- """
- This is the base class for OAuth2 authentication, an instance of it would be used
- as a dependency. All other OAuth2 classes inherit from it and customize it for
- each OAuth2 flow.
-
- You normally would not create a new class inheriting from it but use one of the
- existing subclasses, and maybe compose them if you want to support multiple flows.
-
- Read more about it in the
- [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/).
- """
-
- def __init__(
- self,
- *,
- flows: Annotated[
- Union[OAuthFlowsModel, dict[str, dict[str, Any]]],
- Doc(
- """
- The dictionary of OAuth2 flows.
- """
- ),
- ] = OAuthFlowsModel(),
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if no HTTP Authorization header is provided, required for
- OAuth2 authentication, it will automatically cancel the request and
- send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Authorization header
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, with OAuth2
- or in a cookie).
- """
- ),
- ] = True,
- ):
- self.model = OAuth2Model(
- flows=cast(OAuthFlowsModel, flows), description=description
- )
- self.scheme_name = scheme_name or self.__class__.__name__
- self.auto_error = auto_error
-
- def make_not_authenticated_error(self) -> HTTPException:
- """
- The OAuth 2 specification doesn't define the challenge that should be used,
- because a `Bearer` token is not really the only option to authenticate.
-
- But declaring any other authentication challenge would be application-specific
- as it's not defined in the specification.
-
- For practical reasons, this method uses the `Bearer` challenge by default, as
- it's probably the most common one.
-
- If you are implementing an OAuth2 authentication scheme other than the provided
- ones in FastAPI (based on bearer tokens), you might want to override this.
-
- Ref: https://datatracker.ietf.org/doc/html/rfc6749
- """
- return HTTPException(
- status_code=HTTP_401_UNAUTHORIZED,
- detail="Not authenticated",
- headers={"WWW-Authenticate": "Bearer"},
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- authorization = request.headers.get("Authorization")
- if not authorization:
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return authorization
-
-
-class OAuth2PasswordBearer(OAuth2):
- """
- OAuth2 flow for authentication using a bearer token obtained with a password.
- An instance of it would be used as a dependency.
-
- Read more about it in the
- [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
- """
-
- def __init__(
- self,
- tokenUrl: Annotated[
- str,
- Doc(
- """
- The URL to obtain the OAuth2 token. This would be the *path operation*
- that has `OAuth2PasswordRequestForm` as a dependency.
- """
- ),
- ],
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- scopes: Annotated[
- Optional[dict[str, str]],
- Doc(
- """
- The OAuth2 scopes that would be required by the *path operations* that
- use this dependency.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if no HTTP Authorization header is provided, required for
- OAuth2 authentication, it will automatically cancel the request and
- send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Authorization header
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, with OAuth2
- or in a cookie).
- """
- ),
- ] = True,
- refreshUrl: Annotated[
- Optional[str],
- Doc(
- """
- The URL to refresh the token and obtain a new one.
- """
- ),
- ] = None,
- ):
- if not scopes:
- scopes = {}
- flows = OAuthFlowsModel(
- password=cast(
- Any,
- {
- "tokenUrl": tokenUrl,
- "refreshUrl": refreshUrl,
- "scopes": scopes,
- },
- )
- )
- super().__init__(
- flows=flows,
- scheme_name=scheme_name,
- description=description,
- auto_error=auto_error,
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- authorization = request.headers.get("Authorization")
- scheme, param = get_authorization_scheme_param(authorization)
- if not authorization or scheme.lower() != "bearer":
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return param
-
-
-class OAuth2AuthorizationCodeBearer(OAuth2):
- """
- OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code
- flow. An instance of it would be used as a dependency.
- """
-
- def __init__(
- self,
- authorizationUrl: str,
- tokenUrl: Annotated[
- str,
- Doc(
- """
- The URL to obtain the OAuth2 token.
- """
- ),
- ],
- refreshUrl: Annotated[
- Optional[str],
- Doc(
- """
- The URL to refresh the token and obtain a new one.
- """
- ),
- ] = None,
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- scopes: Annotated[
- Optional[dict[str, str]],
- Doc(
- """
- The OAuth2 scopes that would be required by the *path operations* that
- use this dependency.
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if no HTTP Authorization header is provided, required for
- OAuth2 authentication, it will automatically cancel the request and
- send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Authorization header
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, with OAuth2
- or in a cookie).
- """
- ),
- ] = True,
- ):
- if not scopes:
- scopes = {}
- flows = OAuthFlowsModel(
- authorizationCode=cast(
- Any,
- {
- "authorizationUrl": authorizationUrl,
- "tokenUrl": tokenUrl,
- "refreshUrl": refreshUrl,
- "scopes": scopes,
- },
- )
- )
- super().__init__(
- flows=flows,
- scheme_name=scheme_name,
- description=description,
- auto_error=auto_error,
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- authorization = request.headers.get("Authorization")
- scheme, param = get_authorization_scheme_param(authorization)
- if not authorization or scheme.lower() != "bearer":
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None # pragma: nocover
- return param
-
-
-class SecurityScopes:
- """
- This is a special class that you can define in a parameter in a dependency to
- obtain the OAuth2 scopes required by all the dependencies in the same chain.
-
- This way, multiple dependencies can have different scopes, even when used in the
- same *path operation*. And with this, you can access all the scopes required in
- all those dependencies in a single place.
-
- Read more about it in the
- [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
- """
-
- def __init__(
- self,
- scopes: Annotated[
- Optional[list[str]],
- Doc(
- """
- This will be filled by FastAPI.
- """
- ),
- ] = None,
- ):
- self.scopes: Annotated[
- list[str],
- Doc(
- """
- The list of all the scopes required by dependencies.
- """
- ),
- ] = scopes or []
- self.scope_str: Annotated[
- str,
- Doc(
- """
- All the scopes required by all the dependencies in a single string
- separated by spaces, as defined in the OAuth2 specification.
- """
- ),
- ] = " ".join(self.scopes)
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/open_id_connect_url.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/open_id_connect_url.py
deleted file mode 100644
index f4d9533..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/open_id_connect_url.py
+++ /dev/null
@@ -1,94 +0,0 @@
-from typing import Annotated, Optional
-
-from annotated_doc import Doc
-from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
-from fastapi.security.base import SecurityBase
-from starlette.exceptions import HTTPException
-from starlette.requests import Request
-from starlette.status import HTTP_401_UNAUTHORIZED
-
-
-class OpenIdConnect(SecurityBase):
- """
- OpenID Connect authentication class. An instance of it would be used as a
- dependency.
-
- **Warning**: this is only a stub to connect the components with OpenAPI in FastAPI,
- but it doesn't implement the full OpenIdConnect scheme, for example, it doesn't use
- the OpenIDConnect URL. You would need to to subclass it and implement it in your
- code.
- """
-
- def __init__(
- self,
- *,
- openIdConnectUrl: Annotated[
- str,
- Doc(
- """
- The OpenID Connect URL.
- """
- ),
- ],
- scheme_name: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme name.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- description: Annotated[
- Optional[str],
- Doc(
- """
- Security scheme description.
-
- It will be included in the generated OpenAPI (e.g. visible at `/docs`).
- """
- ),
- ] = None,
- auto_error: Annotated[
- bool,
- Doc(
- """
- By default, if no HTTP Authorization header is provided, required for
- OpenID Connect authentication, it will automatically cancel the request
- and send the client an error.
-
- If `auto_error` is set to `False`, when the HTTP Authorization header
- is not available, instead of erroring out, the dependency result will
- be `None`.
-
- This is useful when you want to have optional authentication.
-
- It is also useful when you want to have authentication that can be
- provided in one of multiple optional ways (for example, with OpenID
- Connect or in a cookie).
- """
- ),
- ] = True,
- ):
- self.model = OpenIdConnectModel(
- openIdConnectUrl=openIdConnectUrl, description=description
- )
- self.scheme_name = scheme_name or self.__class__.__name__
- self.auto_error = auto_error
-
- def make_not_authenticated_error(self) -> HTTPException:
- return HTTPException(
- status_code=HTTP_401_UNAUTHORIZED,
- detail="Not authenticated",
- headers={"WWW-Authenticate": "Bearer"},
- )
-
- async def __call__(self, request: Request) -> Optional[str]:
- authorization = request.headers.get("Authorization")
- if not authorization:
- if self.auto_error:
- raise self.make_not_authenticated_error()
- else:
- return None
- return authorization
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/security/utils.py b/backend/.venv/lib/python3.12/site-packages/fastapi/security/utils.py
deleted file mode 100644
index 002e68b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/security/utils.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Optional
-
-
-def get_authorization_scheme_param(
- authorization_header_value: Optional[str],
-) -> tuple[str, str]:
- if not authorization_header_value:
- return "", ""
- scheme, _, param = authorization_header_value.partition(" ")
- return scheme, param
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/staticfiles.py b/backend/.venv/lib/python3.12/site-packages/fastapi/staticfiles.py
deleted file mode 100644
index 299015d..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/staticfiles.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.staticfiles import StaticFiles as StaticFiles # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/templating.py b/backend/.venv/lib/python3.12/site-packages/fastapi/templating.py
deleted file mode 100644
index 0cb8684..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/templating.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.templating import Jinja2Templates as Jinja2Templates # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/testclient.py b/backend/.venv/lib/python3.12/site-packages/fastapi/testclient.py
deleted file mode 100644
index 4012406..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/testclient.py
+++ /dev/null
@@ -1 +0,0 @@
-from starlette.testclient import TestClient as TestClient # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/types.py b/backend/.venv/lib/python3.12/site-packages/fastapi/types.py
deleted file mode 100644
index d3e980c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/types.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import types
-from enum import Enum
-from typing import Any, Callable, Optional, TypeVar, Union
-
-from pydantic import BaseModel
-
-DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
-UnionType = getattr(types, "UnionType", Union)
-ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str]
-IncEx = Union[set[int], set[str], dict[int, Any], dict[str, Any]]
-DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str]
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/utils.py b/backend/.venv/lib/python3.12/site-packages/fastapi/utils.py
deleted file mode 100644
index 78fdcbb..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/utils.py
+++ /dev/null
@@ -1,164 +0,0 @@
-import re
-import warnings
-from collections.abc import MutableMapping
-from typing import (
- TYPE_CHECKING,
- Any,
- Optional,
- Union,
-)
-from weakref import WeakKeyDictionary
-
-import fastapi
-from fastapi._compat import (
- BaseConfig,
- ModelField,
- PydanticSchemaGenerationError,
- Undefined,
- UndefinedType,
- Validator,
- annotation_is_pydantic_v1,
-)
-from fastapi.datastructures import DefaultPlaceholder, DefaultType
-from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError
-from pydantic import BaseModel
-from pydantic.fields import FieldInfo
-from typing_extensions import Literal
-
-from ._compat import v2
-
-if TYPE_CHECKING: # pragma: nocover
- from .routing import APIRoute
-
-# Cache for `create_cloned_field`
-_CLONED_TYPES_CACHE: MutableMapping[type[BaseModel], type[BaseModel]] = (
- WeakKeyDictionary()
-)
-
-
-def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool:
- if status_code is None:
- return True
- # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1
- if status_code in {
- "default",
- "1XX",
- "2XX",
- "3XX",
- "4XX",
- "5XX",
- }:
- return True
- current_status_code = int(status_code)
- return not (current_status_code < 200 or current_status_code in {204, 205, 304})
-
-
-def get_path_param_names(path: str) -> set[str]:
- return set(re.findall("{(.*?)}", path))
-
-
-_invalid_args_message = (
- "Invalid args for response field! Hint: "
- "check that {type_} is a valid Pydantic field type. "
- "If you are using a return type annotation that is not a valid Pydantic "
- "field (e.g. Union[Response, dict, None]) you can disable generating the "
- "response model from the type annotation with the path operation decorator "
- "parameter response_model=None. Read more: "
- "https://fastapi.tiangolo.com/tutorial/response-model/"
-)
-
-
-def create_model_field(
- name: str,
- type_: Any,
- class_validators: Optional[dict[str, Validator]] = None,
- default: Optional[Any] = Undefined,
- required: Union[bool, UndefinedType] = Undefined,
- model_config: Union[type[BaseConfig], None] = None,
- field_info: Optional[FieldInfo] = None,
- alias: Optional[str] = None,
- mode: Literal["validation", "serialization"] = "validation",
- version: Literal["1", "auto"] = "auto",
-) -> ModelField:
- if annotation_is_pydantic_v1(type_):
- raise PydanticV1NotSupportedError(
- "pydantic.v1 models are no longer supported by FastAPI."
- f" Please update the response model {type_!r}."
- )
- class_validators = class_validators or {}
-
- field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias)
- kwargs = {"mode": mode, "name": name, "field_info": field_info}
- try:
- return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type]
- except PydanticSchemaGenerationError:
- raise fastapi.exceptions.FastAPIError(
- _invalid_args_message.format(type_=type_)
- ) from None
-
-
-def create_cloned_field(
- field: ModelField,
- *,
- cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None,
-) -> ModelField:
- return field
-
-
-def generate_operation_id_for_path(
- *, name: str, path: str, method: str
-) -> str: # pragma: nocover
- warnings.warn(
- message="fastapi.utils.generate_operation_id_for_path() was deprecated, "
- "it is not used internally, and will be removed soon",
- category=FastAPIDeprecationWarning,
- stacklevel=2,
- )
- operation_id = f"{name}{path}"
- operation_id = re.sub(r"\W", "_", operation_id)
- operation_id = f"{operation_id}_{method.lower()}"
- return operation_id
-
-
-def generate_unique_id(route: "APIRoute") -> str:
- operation_id = f"{route.name}{route.path_format}"
- operation_id = re.sub(r"\W", "_", operation_id)
- assert route.methods
- operation_id = f"{operation_id}_{list(route.methods)[0].lower()}"
- return operation_id
-
-
-def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> None:
- for key, value in update_dict.items():
- if (
- key in main_dict
- and isinstance(main_dict[key], dict)
- and isinstance(value, dict)
- ):
- deep_dict_update(main_dict[key], value)
- elif (
- key in main_dict
- and isinstance(main_dict[key], list)
- and isinstance(update_dict[key], list)
- ):
- main_dict[key] = main_dict[key] + update_dict[key]
- else:
- main_dict[key] = value
-
-
-def get_value_or_default(
- first_item: Union[DefaultPlaceholder, DefaultType],
- *extra_items: Union[DefaultPlaceholder, DefaultType],
-) -> Union[DefaultPlaceholder, DefaultType]:
- """
- Pass items or `DefaultPlaceholder`s by descending priority.
-
- The first one to _not_ be a `DefaultPlaceholder` will be returned.
-
- Otherwise, the first item (a `DefaultPlaceholder`) will be returned.
- """
- items = (first_item,) + extra_items
- for item in items:
- if not isinstance(item, DefaultPlaceholder):
- return item
- return first_item
diff --git a/backend/.venv/lib/python3.12/site-packages/fastapi/websockets.py b/backend/.venv/lib/python3.12/site-packages/fastapi/websockets.py
deleted file mode 100644
index 55a4ac4..0000000
--- a/backend/.venv/lib/python3.12/site-packages/fastapi/websockets.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from starlette.websockets import WebSocket as WebSocket # noqa
-from starlette.websockets import WebSocketDisconnect as WebSocketDisconnect # noqa
-from starlette.websockets import WebSocketState as WebSocketState # noqa
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/INSTALLER b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/METADATA b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/METADATA
deleted file mode 100644
index 78655dd..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/METADATA
+++ /dev/null
@@ -1,98 +0,0 @@
-Metadata-Version: 2.4
-Name: greenlet
-Version: 3.3.1
-Summary: Lightweight in-process concurrent programming
-Author-email: Alexey Borzenkov
-Maintainer-email: Jason Madden
-License-Expression: MIT AND Python-2.0
-Project-URL: Homepage, https://greenlet.readthedocs.io
-Project-URL: Documentation, https://greenlet.readthedocs.io
-Project-URL: Repository, https://github.com/python-greenlet/greenlet
-Project-URL: Issues, https://github.com/python-greenlet/greenlet/issues
-Project-URL: Changelog, https://greenlet.readthedocs.io/en/latest/changes.html
-Keywords: greenlet,coroutine,concurrency,threads,cooperative
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Natural Language :: English
-Classifier: Programming Language :: C
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: 3.14
-Classifier: Operating System :: OS Independent
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.10
-Description-Content-Type: text/x-rst
-License-File: LICENSE
-License-File: LICENSE.PSF
-Provides-Extra: docs
-Requires-Dist: Sphinx; extra == "docs"
-Requires-Dist: furo; extra == "docs"
-Provides-Extra: test
-Requires-Dist: objgraph; extra == "test"
-Requires-Dist: psutil; extra == "test"
-Requires-Dist: setuptools; extra == "test"
-Dynamic: license-file
-
-.. This file is included into docs/history.rst
-
-
-Greenlets are lightweight coroutines for in-process concurrent
-programming.
-
-The "greenlet" package is a spin-off of `Stackless`_, a version of
-CPython that supports micro-threads called "tasklets". Tasklets run
-pseudo-concurrently (typically in a single or a few OS-level threads)
-and are synchronized with data exchanges on "channels".
-
-A "greenlet", on the other hand, is a still more primitive notion of
-micro-thread with no implicit scheduling; coroutines, in other words.
-This is useful when you want to control exactly when your code runs.
-You can build custom scheduled micro-threads on top of greenlet;
-however, it seems that greenlets are useful on their own as a way to
-make advanced control flow structures. For example, we can recreate
-generators; the difference with Python's own generators is that our
-generators can call nested functions and the nested functions can
-yield values too. (Additionally, you don't need a "yield" keyword. See
-the example in `test_generator.py
-`_).
-
-Greenlets are provided as a C extension module for the regular unmodified
-interpreter.
-
-.. _`Stackless`: http://www.stackless.com
-
-
-Who is using Greenlet?
-======================
-
-There are several libraries that use Greenlet as a more flexible
-alternative to Python's built in coroutine support:
-
- - `Concurrence`_
- - `Eventlet`_
- - `Gevent`_
-
-.. _Concurrence: http://opensource.hyves.org/concurrence/
-.. _Eventlet: http://eventlet.net/
-.. _Gevent: http://www.gevent.org/
-
-Getting Greenlet
-================
-
-The easiest way to get Greenlet is to install it with pip::
-
- pip install greenlet
-
-
-Source code archives and binary distributions are available on the
-python package index at https://pypi.org/project/greenlet
-
-The source code repository is hosted on github:
-https://github.com/python-greenlet/greenlet
-
-Documentation is available on readthedocs.org:
-https://greenlet.readthedocs.io
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/RECORD b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/RECORD
deleted file mode 100644
index b31a653..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/RECORD
+++ /dev/null
@@ -1,121 +0,0 @@
-../../../include/site/python3.12/greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755
-greenlet-3.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-greenlet-3.3.1.dist-info/METADATA,sha256=qJStBrPq7k5iOECnHy0bMzwrFVWLIwkwXOqNChxFiMw,3734
-greenlet-3.3.1.dist-info/RECORD,,
-greenlet-3.3.1.dist-info/WHEEL,sha256=zinfjLaotCiumpaahaCCWHqF-zUftPolHJRlUFddj9Y,153
-greenlet-3.3.1.dist-info/licenses/LICENSE,sha256=dpgx1uXfrywggC-sz_H6-0wgJd2PYlPfpH_K1Z1NCXk,1434
-greenlet-3.3.1.dist-info/licenses/LICENSE.PSF,sha256=5f88I8EQ5JTNfXNsEP2W1GJFe6_soxCEDbZScpjH1Gs,2424
-greenlet-3.3.1.dist-info/top_level.txt,sha256=YSnRsCRoO61JGlP57o8iKL6rdLWDWuiyKD8ekpWUsDc,9
-greenlet/CObjects.cpp,sha256=OPej1bWBgc4sRrTRQ2aFFML9pzDYKlKhlJSjsI0X_eU,3508
-greenlet/PyGreenlet.cpp,sha256=rEKSIiAJ_IT2k0uZbUbtOpRNUmUcpKFMLLR99dPkFRU,25314
-greenlet/PyGreenlet.hpp,sha256=2ZQlOxYNoy7QwD7mppFoOXe_At56NIsJ0eNsE_hoSsw,1463
-greenlet/PyGreenletUnswitchable.cpp,sha256=PQE0fSZa_IOyUM44IESHkJoD2KtGW3dkhkmZSYY3WHs,4375
-greenlet/PyModule.cpp,sha256=uBC2FOruNKOlcD8FmSI0CkTJkrmRU71aKYwnvY14LvU,8649
-greenlet/TBrokenGreenlet.cpp,sha256=smN26uC7ahAbNYiS10rtWPjCeTG4jevM8siA2sjJiXg,1021
-greenlet/TExceptionState.cpp,sha256=U7Ctw9fBdNraS0d174MoQW7bN-ae209Ta0JuiKpcpVI,1359
-greenlet/TGreenlet.cpp,sha256=8KT_BfvrGW7MZqvWG1sa-QNBygm2aV9ilV2csqqjm6Q,25909
-greenlet/TGreenlet.hpp,sha256=5HkQ_yzOPIySl7AL_1aO-z1WGeXxms8-OLygyWTap_Q,28700
-greenlet/TGreenletGlobals.cpp,sha256=YyEmDjKf1g32bsL-unIUScFLnnA1fzLWf2gOMd-D0Zw,3264
-greenlet/TMainGreenlet.cpp,sha256=pBqCkp_ck3Sv7TJ66g9QV1j7jZt378WpoBozVZ9Wh4M,3420
-greenlet/TPythonState.cpp,sha256=8CSu7xoB6Uliw7l-vRmgKbhPWxT2ByVmm9kfWu7QP3o,17155
-greenlet/TStackState.cpp,sha256=V444I8Jj9DhQz-9leVW_9dtiSRjaE1NMlgDG02Xxq-Y,7381
-greenlet/TThreadState.hpp,sha256=_ntdN33J1IBNucVsCctLtkspkvp3ud0PanVnSxLfj8k,19636
-greenlet/TThreadStateCreator.hpp,sha256=s-PCahFbp8mpTIJTOEDZWMKWWLPA2rC5clZX6tvxA4s,2620
-greenlet/TThreadStateDestroy.cpp,sha256=MShqLtCuUGaQIinFWe2b7Fz-GPmVVKAfV7FbGhb4GEc,8395
-greenlet/TUserGreenlet.cpp,sha256=uemg0lwKXtYB0yzmvyYdIIAsKnNkifXM1OJ2OlrFP1A,23553
-greenlet/__init__.py,sha256=cGKqcKUyFUCstuIEf0siuqEJaJjjl6htIOctdqySU-w,1723
-greenlet/__pycache__/__init__.cpython-312.pyc,,
-greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so,sha256=crrCCJMjcq67_IDN3CCFMhyBEFlLM78ZcCCfxWLXETI,1448736
-greenlet/greenlet.cpp,sha256=menzWlidfN2DfBJYBAcB8BpUOGK1XgMLukeCsERSp_c,11093
-greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755
-greenlet/greenlet_allocator.hpp,sha256=n28rwj76RVSn7B5QDA00nL8OBjfFeiOM1QGrVrHhfsk,1835
-greenlet/greenlet_compiler_compat.hpp,sha256=nRxpLN9iNbnLVyFDeVmOwyeeNm6scQrOed1l7JQYMCM,4346
-greenlet/greenlet_cpython_compat.hpp,sha256=OKLqWp3tmsmighFw9FA5xa4-17GO8P-tXTorErR77ys,4253
-greenlet/greenlet_exceptions.hpp,sha256=06Bx81DtVaJTa6RtiMcV141b-XHv4ppEgVItkblcLWY,4503
-greenlet/greenlet_internal.hpp,sha256=Ajc-_09W4xWzm9XfyXHAeQAFUgKGKsnJwYsTCoNy3ns,2709
-greenlet/greenlet_msvc_compat.hpp,sha256=Lg5xtV5zrRMIGcoieMKmXPhEzXu55aqfM1L8OygnmAY,3195
-greenlet/greenlet_refs.hpp,sha256=OnbA91yZf3QHH6-eJccvoNDAaN-pQBMMrclFU1Ot3J4,34436
-greenlet/greenlet_slp_switch.hpp,sha256=T1Y-w01yBBljePiHgUaWCs3XZSdtHSrtLvvkMXFDUN4,3298
-greenlet/greenlet_thread_support.hpp,sha256=XUJ6ljWjf9OYyuOILiz8e_yHvT3fbaUiHdhiPNQUV4s,867
-greenlet/platform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-greenlet/platform/__pycache__/__init__.cpython-312.pyc,,
-greenlet/platform/setup_switch_x64_masm.cmd,sha256=ZpClUJeU0ujEPSTWNSepP0W2f9XiYQKA8QKSoVou8EU,143
-greenlet/platform/switch_aarch64_gcc.h,sha256=GKC0yWNXnbK2X--X6aguRCMj2Tg7hDU1Zkl3RljDvC8,4307
-greenlet/platform/switch_alpha_unix.h,sha256=Z-SvF8JQV3oxWT8JRbL9RFu4gRFxPdJ7cviM8YayMmw,671
-greenlet/platform/switch_amd64_unix.h,sha256=EcSFCBlodEBhqhKjcJqY_5Dn_jn7pKpkJlOvp7gFXLI,2748
-greenlet/platform/switch_arm32_gcc.h,sha256=Z3KkHszdgq6uU4YN3BxvKMG2AdDnovwCCNrqGWZ1Lyo,2479
-greenlet/platform/switch_arm32_ios.h,sha256=mm5_R9aXB92hyxzFRwB71M60H6AlvHjrpTrc72Pz3l8,1892
-greenlet/platform/switch_arm64_masm.asm,sha256=4kpTtfy7rfcr8j1CpJLAK21EtZpGDAJXWRU68HEy5A8,1245
-greenlet/platform/switch_arm64_masm.obj,sha256=DmLnIB_icoEHAz1naue_pJPTZgR9ElM7-Nmztr-o9_U,746
-greenlet/platform/switch_arm64_msvc.h,sha256=RqK5MHLmXI3Q-FQ7tm32KWnbDNZKnkJdq8CR89cz640,398
-greenlet/platform/switch_csky_gcc.h,sha256=kDikyiPpewP71KoBZQO_MukDTXTXBiC7x-hF0_2DL0w,1331
-greenlet/platform/switch_loongarch64_linux.h,sha256=7M-Dhc4Q8tRbJCJhalDLwU6S9Mx8MjmN1RbTDgIvQTM,779
-greenlet/platform/switch_m68k_gcc.h,sha256=VSa6NpZhvyyvF-Q58CTIWSpEDo4FKygOyTz00whctlw,928
-greenlet/platform/switch_mips_unix.h,sha256=DsbNLh3Nde3WhnK5dwNaVu9r60iAWoCyEemxwBBOHBI,1462
-greenlet/platform/switch_ppc64_aix.h,sha256=_BL0iyRr3ZA5iPlr3uk9SJ5sNRWGYLrXcZ5z-CE9anE,3860
-greenlet/platform/switch_ppc64_linux.h,sha256=0rriT5XyxPb0GqsSSn_bP9iQsnjsPbBmu0yqo5goSyQ,3815
-greenlet/platform/switch_ppc_aix.h,sha256=pHA4slEjUFP3J3SYm1TAlNPhgb2G_PAtax5cO8BEe1A,2941
-greenlet/platform/switch_ppc_linux.h,sha256=YwrlKUzxlXuiKMQqr6MFAV1bPzWnmvk6X1AqJZEpOWU,2759
-greenlet/platform/switch_ppc_macosx.h,sha256=Z6KN_ud0n6nC3ltJrNz2qtvER6vnRAVRNH9mdIDpMxY,2624
-greenlet/platform/switch_ppc_unix.h,sha256=-ZG7MSSPEA5N4qO9PQChtyEJ-Fm6qInhyZm_ZBHTtMg,2652
-greenlet/platform/switch_riscv_unix.h,sha256=606V6ACDf79Fz_WGItnkgbjIJ0pGg_sHmPyDxQYKK58,949
-greenlet/platform/switch_s390_unix.h,sha256=RRlGu957ybmq95qNNY4Qw1mcaoT3eBnW5KbVwu48KX8,2763
-greenlet/platform/switch_sh_gcc.h,sha256=mcRJBTu-2UBf4kZtX601qofwuDuy-Y-hnxJtrcaB7do,901
-greenlet/platform/switch_sparc_sun_gcc.h,sha256=xZish9GsMHBienUbUMsX1-ZZ-as7hs36sVhYIE3ew8Y,2797
-greenlet/platform/switch_x32_unix.h,sha256=nM98PKtzTWc1lcM7TRMUZJzskVdR1C69U1UqZRWX0GE,1509
-greenlet/platform/switch_x64_masm.asm,sha256=nu6n2sWyXuXfpPx40d9YmLfHXUc1sHgeTvX1kUzuvEM,1841
-greenlet/platform/switch_x64_masm.obj,sha256=GNtTNxYdo7idFUYsQv-mrXWgyT5EJ93-9q90lN6svtQ,1078
-greenlet/platform/switch_x64_msvc.h,sha256=LIeasyKo_vHzspdMzMHbosRhrBfKI4BkQOh4qcTHyJw,1805
-greenlet/platform/switch_x86_msvc.h,sha256=TtGOwinbFfnn6clxMNkCz8i6OmgB6kVRrShoF5iT9to,12838
-greenlet/platform/switch_x86_unix.h,sha256=VplW9H0FF0cZHw1DhJdIUs5q6YLS4cwb2nYwjF83R1s,3059
-greenlet/slp_platformselect.h,sha256=hTb3GFdcPUYJTuu1MY93js7MZEax1_e5E-gflpi0RzI,3959
-greenlet/tests/__init__.py,sha256=EtTtQfpRDde0MhsdAM5Cm7LYIfS_HKUIFwquiH4Q7ac,9736
-greenlet/tests/__pycache__/__init__.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc,,
-greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc,,
-greenlet/tests/__pycache__/leakcheck.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_cpp.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_gc.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_generator.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_leaks.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_throw.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_tracing.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_version.cpython-312.pyc,,
-greenlet/tests/__pycache__/test_weakref.cpython-312.pyc,,
-greenlet/tests/_test_extension.c,sha256=DETtCa8cvPgQ2KrSQm9jlqZSlb_1x0o3axydmzgPohQ,6921
-greenlet/tests/_test_extension.cpython-312-x86_64-linux-gnu.so,sha256=cHhnCREBuPv6JwdbVNDHxaYxSOFjzJtzwlO8ihqP1Uo,17256
-greenlet/tests/_test_extension_cpp.cpp,sha256=VbkGmOw9b6pnj5OsqQa7OC5aPfIBynx-aVQXXF9uWcE,6686
-greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so,sha256=kpF8yFVUZn8HGv8t3b3iw9PrPAocM49Mm8ZlFPr-Vrw,58384
-greenlet/tests/fail_clearing_run_switches.py,sha256=o433oA_nUCtOPaMEGc8VEhZIKa71imVHXFw7TsXaP8M,1263
-greenlet/tests/fail_cpp_exception.py,sha256=o_ZbipWikok8Bjc-vjiQvcb5FHh2nVW-McGKMLcMzh0,985
-greenlet/tests/fail_initialstub_already_started.py,sha256=txENn5IyzGx2p-XR1XB7qXmC8JX_4mKDEA8kYBXUQKc,1961
-greenlet/tests/fail_slp_switch.py,sha256=rJBZcZfTWR3e2ERQtPAud6YKShiDsP84PmwOJbp4ey0,524
-greenlet/tests/fail_switch_three_greenlets.py,sha256=zSitV7rkNnaoHYVzAGGLnxz-yPtohXJJzaE8ehFDQ0M,956
-greenlet/tests/fail_switch_three_greenlets2.py,sha256=FPJensn2EJxoropl03JSTVP3kgP33k04h6aDWWozrOk,1285
-greenlet/tests/fail_switch_two_greenlets.py,sha256=1CaI8s3504VbbF1vj1uBYuy-zxBHVzHPIAd1LIc8ONg,817
-greenlet/tests/leakcheck.py,sha256=f28zZf0MlMgcybbm_5YyIOR6HcDqP2f2k6LX-FxFGqA,12652
-greenlet/tests/test_contextvars.py,sha256=xutO-qZgKTwKsA9lAqTjIcTBEiQV4RpNKM-vO2_YCVU,10541
-greenlet/tests/test_cpp.py,sha256=hpxhFAdKJTpAVZP8CBGs1ZcrKdscI9BaDZk4btkI5d4,2736
-greenlet/tests/test_extension_interface.py,sha256=eJ3cwLacdK2WbsrC-4DgeyHdwLRcG4zx7rrkRtqSzC4,3829
-greenlet/tests/test_gc.py,sha256=xrIreQr85eO8WlpHs6IWCa5C4ecIA6t2_IrkS76Fdjg,2922
-greenlet/tests/test_generator.py,sha256=tONXiTf98VGm347o1b-810daPiwdla5cbpFg6QI1R1g,1240
-greenlet/tests/test_generator_nested.py,sha256=7v4HOYrf1XZP39dk5IUMubdZ8yc3ynwZcqj9GUJyMSA,3718
-greenlet/tests/test_greenlet.py,sha256=oRrUAGEFb-GF8GNVEFsXYvzrayleF5qQDdA2QWeYEis,48439
-greenlet/tests/test_greenlet_trash.py,sha256=n2dBlQfOoEO1ODatFi8QdhboH3fB86YtqzcYMYOXxbw,7947
-greenlet/tests/test_leaks.py,sha256=OFSE870Zyql85HukfC_XYa2c4gDQBU889RV1AlLum74,18076
-greenlet/tests/test_stack_saved.py,sha256=eyzqNY2VCGuGlxhT_In6TvZ6Okb0AXFZVyBEnK1jDwA,446
-greenlet/tests/test_throw.py,sha256=u2TQ_WvvCd6N6JdXWIxVEcXkKu5fepDlz9dktYdmtng,3712
-greenlet/tests/test_tracing.py,sha256=NFD6Vcww8grBnFQFhCNdswwGetjLeLQ7vL2Qqw3LWBM,8591
-greenlet/tests/test_version.py,sha256=O9DpAITsOFgiRcjd4odQ7ejmwx_N9Q1zQENVcbtFHIc,1339
-greenlet/tests/test_weakref.py,sha256=F8M23btEF87bIbpptLNBORosbQqNZGiYeKMqYjWrsak,883
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/WHEEL b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/WHEEL
deleted file mode 100644
index a737a79..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/WHEEL
+++ /dev/null
@@ -1,6 +0,0 @@
-Wheel-Version: 1.0
-Generator: setuptools (80.10.1)
-Root-Is-Purelib: false
-Tag: cp312-cp312-manylinux_2_24_x86_64
-Tag: cp312-cp312-manylinux_2_28_x86_64
-
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE
deleted file mode 100644
index b73a4a1..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE
+++ /dev/null
@@ -1,30 +0,0 @@
-The following files are derived from Stackless Python and are subject to the
-same license as Stackless Python:
-
- src/greenlet/slp_platformselect.h
- files in src/greenlet/platform/ directory
-
-See LICENSE.PSF and http://www.stackless.com/ for details.
-
-Unless otherwise noted, the files in greenlet have been released under the
-following MIT license:
-
-Copyright (c) Armin Rigo, Christian Tismer and contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE.PSF b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE.PSF
deleted file mode 100644
index d3b509a..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/licenses/LICENSE.PSF
+++ /dev/null
@@ -1,47 +0,0 @@
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF hereby
-grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
-analyze, test, perform and/or display publicly, prepare derivative works,
-distribute, and otherwise use Python alone or in any derivative version,
-provided, however, that PSF's License Agreement and PSF's notice of copyright,
-i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-2011 Python Software Foundation; All Rights Reserved" are retained in Python
-alone or in any derivative version prepared by Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
-
-4. PSF is making Python available to Licensee on an "AS IS"
-basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee. This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/top_level.txt b/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/top_level.txt
deleted file mode 100644
index 46725be..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet-3.3.1.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-greenlet
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/CObjects.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/CObjects.cpp
deleted file mode 100644
index c135995..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/CObjects.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-#ifndef COBJECTS_CPP
-#define COBJECTS_CPP
-/*****************************************************************************
- * C interface
- *
- * These are exported using the CObject API
- */
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wunused-function"
-#endif
-
-#include "greenlet_exceptions.hpp"
-
-#include "greenlet_internal.hpp"
-#include "greenlet_refs.hpp"
-
-
-#include "TThreadStateDestroy.cpp"
-
-#include "PyGreenlet.hpp"
-
-using greenlet::PyErrOccurred;
-using greenlet::Require;
-
-
-
-extern "C" {
-static PyGreenlet*
-PyGreenlet_GetCurrent(void)
-{
- return GET_THREAD_STATE().state().get_current().relinquish_ownership();
-}
-
-static int
-PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent)
-{
- return green_setparent((PyGreenlet*)g, (PyObject*)nparent, NULL);
-}
-
-static PyGreenlet*
-PyGreenlet_New(PyObject* run, PyGreenlet* parent)
-{
- using greenlet::refs::NewDictReference;
- // In the past, we didn't use green_new and green_init, but that
- // was a maintenance issue because we duplicated code. This way is
- // much safer, but slightly slower. If that's a problem, we could
- // refactor green_init to separate argument parsing from initialization.
- OwnedGreenlet g = OwnedGreenlet::consuming(green_new(&PyGreenlet_Type, nullptr, nullptr));
- if (!g) {
- return NULL;
- }
-
- try {
- NewDictReference kwargs;
- if (run) {
- kwargs.SetItem(mod_globs->str_run, run);
- }
- if (parent) {
- kwargs.SetItem("parent", (PyObject*)parent);
- }
-
- Require(green_init(g.borrow(), mod_globs->empty_tuple, kwargs.borrow()));
- }
- catch (const PyErrOccurred&) {
- return nullptr;
- }
-
- return g.relinquish_ownership();
-}
-
-static PyObject*
-PyGreenlet_Switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return NULL;
- }
-
- if (args == NULL) {
- args = mod_globs->empty_tuple;
- }
-
- if (kwargs == NULL || !PyDict_Check(kwargs)) {
- kwargs = NULL;
- }
-
- return green_switch(self, args, kwargs);
-}
-
-static PyObject*
-PyGreenlet_Throw(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return nullptr;
- }
- try {
- PyErrPieces err_pieces(typ, val, tb);
- return internal_green_throw(self, err_pieces).relinquish_ownership();
- }
- catch (const PyErrOccurred&) {
- return nullptr;
- }
-}
-
-
-
-static int
-Extern_PyGreenlet_MAIN(PyGreenlet* self)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return -1;
- }
- return self->pimpl->main();
-}
-
-static int
-Extern_PyGreenlet_ACTIVE(PyGreenlet* self)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return -1;
- }
- return self->pimpl->active();
-}
-
-static int
-Extern_PyGreenlet_STARTED(PyGreenlet* self)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return -1;
- }
- return self->pimpl->started();
-}
-
-static PyGreenlet*
-Extern_PyGreenlet_GET_PARENT(PyGreenlet* self)
-{
- if (!PyGreenlet_Check(self)) {
- PyErr_BadArgument();
- return NULL;
- }
- // This can return NULL even if there is no exception
- return self->pimpl->parent().acquire();
-}
-} // extern C.
-
-/** End C API ****************************************************************/
-#ifdef __clang__
-# pragma clang diagnostic pop
-#endif
-
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp
deleted file mode 100644
index fd62241..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp
+++ /dev/null
@@ -1,774 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-#ifndef PYGREENLET_CPP
-#define PYGREENLET_CPP
-/*****************
-The Python slot functions for TGreenlet.
- */
-
-
-#define PY_SSIZE_T_CLEAN
-#include
-#include "structmember.h" // PyMemberDef
-
-#include "greenlet_internal.hpp"
-#include "TThreadStateDestroy.cpp"
-#include "TGreenlet.hpp"
-// #include "TUserGreenlet.cpp"
-// #include "TMainGreenlet.cpp"
-// #include "TBrokenGreenlet.cpp"
-
-
-#include "greenlet_refs.hpp"
-#include "greenlet_slp_switch.hpp"
-
-#include "greenlet_thread_support.hpp"
-#include "TGreenlet.hpp"
-
-#include "TGreenletGlobals.cpp"
-#include "TThreadStateDestroy.cpp"
-#include "PyGreenlet.hpp"
-// #include "TGreenlet.cpp"
-
-// #include "TExceptionState.cpp"
-// #include "TPythonState.cpp"
-// #include "TStackState.cpp"
-
-using greenlet::LockGuard;
-using greenlet::LockInitError;
-using greenlet::PyErrOccurred;
-using greenlet::Require;
-
-using greenlet::g_handle_exit;
-using greenlet::single_result;
-
-using greenlet::Greenlet;
-using greenlet::UserGreenlet;
-using greenlet::MainGreenlet;
-using greenlet::BrokenGreenlet;
-using greenlet::ThreadState;
-using greenlet::PythonState;
-
-
-
-static PyGreenlet*
-green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
-{
- PyGreenlet* o =
- (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
- if (o) {
- // Recall: borrowing or getting the current greenlet
- // causes the "deleteme list" to get cleared. So constructing a greenlet
- // can do things like cause other greenlets to get finalized.
- UserGreenlet* c = new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current());
- assert(Py_REFCNT(o) == 1);
- // Also: This looks like a memory leak, but isn't. Constructing the
- // C++ object assigns it to the pimpl pointer of the Python object (o);
- // we'll need that later.
- assert(c == o->pimpl);
- }
- return o;
-}
-
-
-// green_init is used in the tp_init slot. So it's important that
-// it can be called directly from CPython. Thus, we don't use
-// BorrowedGreenlet and BorrowedObject --- although in theory
-// these should be binary layout compatible, that may not be
-// guaranteed to be the case (32-bit linux ppc possibly).
-static int
-green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs)
-{
- PyArgParseParam run;
- PyArgParseParam nparent;
- static const char* kwlist[] = {
- "run",
- "parent",
- NULL
- };
-
- // recall: The O specifier does NOT increase the reference count.
- if (!PyArg_ParseTupleAndKeywords(
- args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) {
- return -1;
- }
-
- if (run) {
- if (green_setrun(self, run, NULL)) {
- return -1;
- }
- }
- if (nparent && !nparent.is_None()) {
- return green_setparent(self, nparent, NULL);
- }
- return 0;
-}
-
-
-
-static int
-green_traverse(PyGreenlet* self, visitproc visit, void* arg)
-{
- // We must only visit referenced objects, i.e. only objects
- // Py_INCREF'ed by this greenlet (directly or indirectly):
- //
- // - stack_prev is not visited: holds previous stack pointer, but it's not
- // referenced
- // - frames are not visited as we don't strongly reference them;
- // alive greenlets are not garbage collected
- // anyway. This can be a problem, however, if this greenlet is
- // never allowed to finish, and is referenced from the frame: we
- // have an uncollectible cycle in that case. Note that the
- // frame object itself is also frequently not even tracked by the GC
- // starting with Python 3.7 (frames are allocated by the
- // interpreter untracked, and only become tracked when their
- // evaluation is finished if they have a refcount > 1). All of
- // this is to say that we should probably strongly reference
- // the frame object. Doing so, while always allowing GC on a
- // greenlet, solves several leaks for us.
-
- Py_VISIT(self->dict);
- if (!self->pimpl) {
- // Hmm. I have seen this at interpreter shutdown time,
- // I think. That's very odd because this doesn't go away until
- // we're ``green_dealloc()``, at which point we shouldn't be
- // traversed anymore.
- return 0;
- }
-
- return self->pimpl->tp_traverse(visit, arg);
-}
-
-static int
-green_is_gc(PyObject* _self)
-{
- BorrowedGreenlet self(_self);
- int result = 0;
- /* Main greenlet can be garbage collected since it can only
- become unreachable if the underlying thread exited.
- Active greenlets --- including those that are suspended ---
- cannot be garbage collected, however.
- */
- if (self->main() || !self->active()) {
- result = 1;
- }
- // The main greenlet pointer will eventually go away after the thread dies.
- if (self->was_running_in_dead_thread()) {
- // Our thread is dead! We can never run again. Might as well
- // GC us. Note that if a tuple containing only us and other
- // immutable objects had been scanned before this, when we
- // would have returned 0, the tuple will take itself out of GC
- // tracking and never be investigated again. So that could
- // result in both us and the tuple leaking due to an
- // unreachable/uncollectible reference. The same goes for
- // dictionaries.
- //
- // It's not a great idea to be changing our GC state on the
- // fly.
- result = 1;
- }
- return result;
-}
-
-
-static int
-green_clear(PyGreenlet* self)
-{
- /* Greenlet is only cleared if it is about to be collected.
- Since active greenlets are not garbage collectable, we can
- be sure that, even if they are deallocated during clear,
- nothing they reference is in unreachable or finalizers,
- so even if it switches we are relatively safe. */
- // XXX: Are we responsible for clearing weakrefs here?
- Py_CLEAR(self->dict);
- return self->pimpl->tp_clear();
-}
-
-/**
- * Returns 0 on failure (the object was resurrected) or 1 on success.
- **/
-static int
-_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self)
-{
- /* Hacks hacks hacks copied from instance_dealloc() */
- /* Temporarily resurrect the greenlet. */
- assert(self.REFCNT() == 0);
- Py_SET_REFCNT(self.borrow(), 1);
- /* Save the current exception, if any. */
- PyErrPieces saved_err;
- try {
- // BY THE TIME WE GET HERE, the state may actually be going
- // away
- // if we're shutting down the interpreter and freeing thread
- // entries,
- // this could result in freeing greenlets that were leaked. So
- // we can't try to read the state.
- self->deallocing_greenlet_in_thread(
- self->thread_state()
- ? static_cast(GET_THREAD_STATE())
- : nullptr);
- }
- catch (const PyErrOccurred&) {
- PyErr_WriteUnraisable(self.borrow_o());
- /* XXX what else should we do? */
- }
- /* Check for no resurrection must be done while we keep
- * our internal reference, otherwise PyFile_WriteObject
- * causes recursion if using Py_INCREF/Py_DECREF
- */
- if (self.REFCNT() == 1 && self->active()) {
- /* Not resurrected, but still not dead!
- XXX what else should we do? we complain. */
- PyObject* f = PySys_GetObject("stderr");
- Py_INCREF(self.borrow_o()); /* leak! */
- if (f != NULL) {
- PyFile_WriteString("GreenletExit did not kill ", f);
- PyFile_WriteObject(self.borrow_o(), f, 0);
- PyFile_WriteString("\n", f);
- }
- }
- /* Restore the saved exception. */
- saved_err.PyErrRestore();
- /* Undo the temporary resurrection; can't use DECREF here,
- * it would cause a recursive call.
- */
- assert(self.REFCNT() > 0);
-
- Py_ssize_t refcnt = self.REFCNT() - 1;
- Py_SET_REFCNT(self.borrow_o(), refcnt);
- if (refcnt != 0) {
- /* Resurrected! */
- _Py_NewReference(self.borrow_o());
- Py_SET_REFCNT(self.borrow_o(), refcnt);
- /* Better to use tp_finalizer slot (PEP 442)
- * and call ``PyObject_CallFinalizerFromDealloc``,
- * but that's only supported in Python 3.4+; see
- * Modules/_io/iobase.c for an example.
- * TODO: We no longer run on anything that old, switch to finalizers.
- *
- * The following approach is copied from iobase.c in CPython 2.7.
- * (along with much of this function in general). Here's their
- * comment:
- *
- * When called from a heap type's dealloc, the type will be
- * decref'ed on return (see e.g. subtype_dealloc in typeobject.c).
- *
- * On free-threaded builds of CPython, the type is meant to be immortal
- * so we probably shouldn't mess with this? See
- * test_issue_245_reference_counting_subclass_no_threads
- */
- if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) {
- Py_INCREF(self.TYPE());
- }
-
- PyObject_GC_Track((PyObject*)self);
-
- GREENLET_Py_DEC_REFTOTAL;
-#ifdef COUNT_ALLOCS
- --Py_TYPE(self)->tp_frees;
- --Py_TYPE(self)->tp_allocs;
-#endif /* COUNT_ALLOCS */
- return 0;
- }
- return 1;
-}
-
-
-static void
-green_dealloc(PyGreenlet* self)
-{
- PyObject_GC_UnTrack(self);
- BorrowedGreenlet me(self);
- if (me->active()
- && me->started()
- && !me->main()) {
- if (!_green_dealloc_kill_started_non_main_greenlet(me)) {
- return;
- }
- }
-
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs((PyObject*)self);
- }
- Py_CLEAR(self->dict);
-
- if (self->pimpl) {
- // In case deleting this, which frees some memory,
- // somehow winds up calling back into us. That's usually a
- //bug in our code.
- Greenlet* p = self->pimpl;
- self->pimpl = nullptr;
- delete p;
- }
- // and finally we're done. self is now invalid.
- Py_TYPE(self)->tp_free((PyObject*)self);
-}
-
-
-
-static OwnedObject
-internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces)
-{
- PyObject* result = nullptr;
- err_pieces.PyErrRestore();
- assert(PyErr_Occurred());
- if (self->started() && !self->active()) {
- /* dead greenlet: turn GreenletExit into a regular return */
- result = g_handle_exit(OwnedObject()).relinquish_ownership();
- }
- self->args() <<= result;
-
- return single_result(self->g_switch());
-}
-
-
-
-PyDoc_STRVAR(
- green_switch_doc,
- "switch(*args, **kwargs)\n"
- "\n"
- "Switch execution to this greenlet.\n"
- "\n"
- "If this greenlet has never been run, then this greenlet\n"
- "will be switched to using the body of ``self.run(*args, **kwargs)``.\n"
- "\n"
- "If the greenlet is active (has been run, but was switch()'ed\n"
- "out before leaving its run function), then this greenlet will\n"
- "be resumed and the return value to its switch call will be\n"
- "None if no arguments are given, the given argument if one\n"
- "argument is given, or the args tuple and keyword args dict if\n"
- "multiple arguments are given.\n"
- "\n"
- "If the greenlet is dead, or is the current greenlet then this\n"
- "function will simply return the arguments using the same rules as\n"
- "above.\n");
-
-static PyObject*
-green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
-{
- using greenlet::SwitchingArgs;
- SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs));
- self->pimpl->may_switch_away();
- self->pimpl->args() <<= switch_args;
-
- // If we're switching out of a greenlet, and that switch is the
- // last thing the greenlet does, the greenlet ought to be able to
- // go ahead and die at that point. Currently, someone else must
- // manually switch back to the greenlet so that we "fall off the
- // end" and can perform cleanup. You'd think we'd be able to
- // figure out that this is happening using the frame's ``f_lasti``
- // member, which is supposed to be an index into
- // ``frame->f_code->co_code``, the bytecode string. However, in
- // recent interpreters, ``f_lasti`` tends not to be updated thanks
- // to things like the PREDICT() macros in ceval.c. So it doesn't
- // really work to do that in many cases. For example, the Python
- // code:
- // def run():
- // greenlet.getcurrent().parent.switch()
- // produces bytecode of len 16, with the actual call to switch()
- // being at index 10 (in Python 3.10). However, the reported
- // ``f_lasti`` we actually see is...5! (Which happens to be the
- // second byte of the CALL_METHOD op for ``getcurrent()``).
-
- try {
- //OwnedObject result = single_result(self->pimpl->g_switch());
- OwnedObject result(single_result(self->pimpl->g_switch()));
-#ifndef NDEBUG
- // Note that the current greenlet isn't necessarily self. If self
- // finished, we went to one of its parents.
- assert(!self->pimpl->args());
-
- const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current();
- // It's possible it's never been switched to.
- assert(!current->args());
-#endif
- PyObject* p = result.relinquish_ownership();
-
- if (!p && !PyErr_Occurred()) {
- // This shouldn't be happening anymore, so the asserts
- // are there for debug builds. Non-debug builds
- // crash "gracefully" in this case, although there is an
- // argument to be made for killing the process in all
- // cases --- for this to be the case, our switches
- // probably nested in an incorrect way, so the state is
- // suspicious. Nothing should be corrupt though, just
- // confused at the Python level. Letting this propagate is
- // probably good enough.
- assert(p || PyErr_Occurred());
- throw PyErrOccurred(
- mod_globs->PyExc_GreenletError,
- "Greenlet.switch() returned NULL without an exception set."
- );
- }
- return p;
- }
- catch(const PyErrOccurred&) {
- return nullptr;
- }
-}
-
-PyDoc_STRVAR(
- green_throw_doc,
- "Switches execution to this greenlet, but immediately raises the\n"
- "given exception in this greenlet. If no argument is provided, the "
- "exception\n"
- "defaults to `greenlet.GreenletExit`. The normal exception\n"
- "propagation rules apply, as described for `switch`. Note that calling "
- "this\n"
- "method is almost equivalent to the following::\n"
- "\n"
- " def raiser():\n"
- " raise typ, val, tb\n"
- " g_raiser = greenlet(raiser, parent=g)\n"
- " g_raiser.switch()\n"
- "\n"
- "except that this trick does not work for the\n"
- "`greenlet.GreenletExit` exception, which would not propagate\n"
- "from ``g_raiser`` to ``g``.\n");
-
-static PyObject*
-green_throw(PyGreenlet* self, PyObject* args)
-{
- PyArgParseParam typ(mod_globs->PyExc_GreenletExit);
- PyArgParseParam val;
- PyArgParseParam tb;
-
- if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) {
- return nullptr;
- }
-
- assert(typ.borrow() || val.borrow());
-
- self->pimpl->may_switch_away();
- try {
- // Both normalizing the error and the actual throw_greenlet
- // could throw PyErrOccurred.
- PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow());
-
- return internal_green_throw(self, err_pieces).relinquish_ownership();
- }
- catch (const PyErrOccurred&) {
- return nullptr;
- }
-}
-
-static int
-green_bool(PyGreenlet* self)
-{
- return self->pimpl->active();
-}
-
-/**
- * CAUTION: Allocates memory, may run GC and arbitrary Python code.
- */
-static PyObject*
-green_getdict(PyGreenlet* self, void* UNUSED(context))
-{
- if (self->dict == NULL) {
- self->dict = PyDict_New();
- if (self->dict == NULL) {
- return NULL;
- }
- }
- Py_INCREF(self->dict);
- return self->dict;
-}
-
-static int
-green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context))
-{
- PyObject* tmp;
-
- if (val == NULL) {
- PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted");
- return -1;
- }
- if (!PyDict_Check(val)) {
- PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary");
- return -1;
- }
- tmp = self->dict;
- Py_INCREF(val);
- self->dict = val;
- Py_XDECREF(tmp);
- return 0;
-}
-
-static bool
-_green_not_dead(BorrowedGreenlet self)
-{
- // XXX: Where else should we do this?
- // Probably on entry to most Python-facing functions?
- if (self->was_running_in_dead_thread()) {
- self->deactivate_and_free();
- return false;
- }
- return self->active() || !self->started();
-}
-
-
-static PyObject*
-green_getdead(PyGreenlet* self, void* UNUSED(context))
-{
- if (_green_not_dead(self)) {
- Py_RETURN_FALSE;
- }
- else {
- Py_RETURN_TRUE;
- }
-}
-
-static PyObject*
-green_get_stack_saved(PyGreenlet* self, void* UNUSED(context))
-{
- return PyLong_FromSsize_t(self->pimpl->stack_saved());
-}
-
-
-static PyObject*
-green_getrun(PyGreenlet* self, void* UNUSED(context))
-{
- try {
- OwnedObject result(BorrowedGreenlet(self)->run());
- return result.relinquish_ownership();
- }
- catch(const PyErrOccurred&) {
- return nullptr;
- }
-}
-
-
-static int
-green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context))
-{
- try {
- BorrowedGreenlet(self)->run(nrun);
- return 0;
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
-}
-
-static PyObject*
-green_getparent(PyGreenlet* self, void* UNUSED(context))
-{
- return BorrowedGreenlet(self)->parent().acquire_or_None();
-}
-
-
-static int
-green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context))
-{
- try {
- BorrowedGreenlet(self)->parent(nparent);
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
- return 0;
-}
-
-
-static PyObject*
-green_getcontext(const PyGreenlet* self, void* UNUSED(context))
-{
- const Greenlet *const g = self->pimpl;
- try {
- OwnedObject result(g->context());
- return result.relinquish_ownership();
- }
- catch(const PyErrOccurred&) {
- return nullptr;
- }
-}
-
-static int
-green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context))
-{
- try {
- BorrowedGreenlet(self)->context(nctx);
- return 0;
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
-}
-
-
-static PyObject*
-green_getframe(PyGreenlet* self, void* UNUSED(context))
-{
- const PythonState::OwnedFrame& top_frame = BorrowedGreenlet(self)->top_frame();
- return top_frame.acquire_or_None();
-}
-
-
-static PyObject*
-green_getstate(PyGreenlet* self)
-{
- PyErr_Format(PyExc_TypeError,
- "cannot serialize '%s' object",
- Py_TYPE(self)->tp_name);
- return nullptr;
-}
-
-static PyObject*
-green_repr(PyGreenlet* _self)
-{
- BorrowedGreenlet self(_self);
- /*
- Return a string like
-
-
- The handling of greenlets across threads is not super good.
- We mostly use the internal definitions of these terms, but they
- generally should make sense to users as well.
- */
- PyObject* result;
- int never_started = !self->started() && !self->active();
-
- const char* const tp_name = Py_TYPE(self)->tp_name;
-
- if (_green_not_dead(self)) {
- /* XXX: The otid= is almost useless because you can't correlate it to
- any thread identifier exposed to Python. We could use
- PyThreadState_GET()->thread_id, but we'd need to save that in the
- greenlet, or save the whole PyThreadState object itself.
-
- As it stands, its only useful for identifying greenlets from the same thread.
- */
- const char* state_in_thread;
- if (self->was_running_in_dead_thread()) {
- // The thread it was running in is dead!
- // This can happen, especially at interpreter shut down.
- // It complicates debugging output because it may be
- // impossible to access the current thread state at that
- // time. Thus, don't access the current thread state.
- state_in_thread = " (thread exited)";
- }
- else {
- state_in_thread = GET_THREAD_STATE().state().is_current(self)
- ? " current"
- : (self->started() ? " suspended" : "");
- }
- result = PyUnicode_FromFormat(
- "<%s object at %p (otid=%p)%s%s%s%s>",
- tp_name,
- self.borrow_o(),
- self->thread_state(),
- state_in_thread,
- self->active() ? " active" : "",
- never_started ? " pending" : " started",
- self->main() ? " main" : ""
- );
- }
- else {
- result = PyUnicode_FromFormat(
- "<%s object at %p (otid=%p) %sdead>",
- tp_name,
- self.borrow_o(),
- self->thread_state(),
- self->was_running_in_dead_thread()
- ? "(thread exited) "
- : ""
- );
- }
-
- return result;
-}
-
-
-static PyMethodDef green_methods[] = {
- {
- .ml_name="switch",
- .ml_meth=reinterpret_cast(green_switch),
- .ml_flags=METH_VARARGS | METH_KEYWORDS,
- .ml_doc=green_switch_doc
- },
- {.ml_name="throw", .ml_meth=(PyCFunction)green_throw, .ml_flags=METH_VARARGS, .ml_doc=green_throw_doc},
- {.ml_name="__getstate__", .ml_meth=(PyCFunction)green_getstate, .ml_flags=METH_NOARGS, .ml_doc=NULL},
- {.ml_name=NULL, .ml_meth=NULL} /* sentinel */
-};
-
-static PyGetSetDef green_getsets[] = {
- /* name, getter, setter, doc, context pointer */
- {.name="__dict__", .get=(getter)green_getdict, .set=(setter)green_setdict},
- {.name="run", .get=(getter)green_getrun, .set=(setter)green_setrun},
- {.name="parent", .get=(getter)green_getparent, .set=(setter)green_setparent},
- {.name="gr_frame", .get=(getter)green_getframe },
- {
- .name="gr_context",
- .get=(getter)green_getcontext,
- .set=(setter)green_setcontext
- },
- {.name="dead", .get=(getter)green_getdead},
- {.name="_stack_saved", .get=(getter)green_get_stack_saved},
- {.name=NULL}
-};
-
-static PyMemberDef green_members[] = {
- {.name=NULL}
-};
-
-static PyNumberMethods green_as_number = {
- .nb_bool=(inquiry)green_bool,
-};
-
-
-PyTypeObject PyGreenlet_Type = {
- .ob_base=PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name="greenlet.greenlet", /* tp_name */
- .tp_basicsize=sizeof(PyGreenlet), /* tp_basicsize */
- /* methods */
- .tp_dealloc=(destructor)green_dealloc, /* tp_dealloc */
- .tp_repr=(reprfunc)green_repr, /* tp_repr */
- .tp_as_number=&green_as_number, /* tp_as _number*/
- .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- .tp_doc="greenlet(run=None, parent=None) -> greenlet\n\n"
- "Creates a new greenlet object (without running it).\n\n"
- " - *run* -- The callable to invoke.\n"
- " - *parent* -- The parent greenlet. The default is the current "
- "greenlet.", /* tp_doc */
- .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */
- .tp_clear=(inquiry)green_clear, /* tp_clear */
- .tp_weaklistoffset=offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */
-
- .tp_methods=green_methods, /* tp_methods */
- .tp_members=green_members, /* tp_members */
- .tp_getset=green_getsets, /* tp_getset */
- .tp_dictoffset=offsetof(PyGreenlet, dict), /* tp_dictoffset */
- .tp_init=(initproc)green_init, /* tp_init */
- .tp_alloc=PyType_GenericAlloc, /* tp_alloc */
- .tp_new=(newfunc)green_new, /* tp_new */
- .tp_free=PyObject_GC_Del, /* tp_free */
-#ifndef Py_GIL_DISABLED
-/*
- We may have been handling this wrong all along.
-
- It shows as a problem with the GIL disabled. In builds of 3.14 with
- assertions enabled, we break the garbage collector if we *ever*
- return false from this function. The docs say this is to distinguish
- some objects that are collectable vs some that are not, specifically
- giving the example of PyTypeObject as the only place this is done,
- where it distinguishes between static types like this one (allocated
- by the C runtime at load time) and dynamic heap types (created at
- runtime as objects). With the GIL disabled, all allocations that are
- potentially collectable go in the mimalloc heap, and the collector
- asserts that tp_is_gc() is true for them as it walks through the
- heap object by object. Since we set the Py_TPFLAGS_HAS_GC bit, we
- are always allocated in that mimalloc heap, so we must always be
- collectable.
-
- XXX: TODO: Could this be responsible for some apparent leaks, even
- on GIL builds, at least in 3.14? See if we can catch an assertion
- failure in the GC on regular 3.14 as well.
- */
- .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
-#endif
-};
-
-#endif
-
-// Local Variables:
-// flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8")
-// End:
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp
deleted file mode 100644
index df6cd80..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#ifndef PYGREENLET_HPP
-#define PYGREENLET_HPP
-
-
-#include "greenlet.h"
-#include "greenlet_compiler_compat.hpp"
-#include "greenlet_refs.hpp"
-
-
-using greenlet::refs::OwnedGreenlet;
-using greenlet::refs::BorrowedGreenlet;
-using greenlet::refs::BorrowedObject;;
-using greenlet::refs::OwnedObject;
-using greenlet::refs::PyErrPieces;
-
-
-// XXX: These doesn't really belong here, it's not a Python slot.
-static OwnedObject internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces);
-
-static PyGreenlet* green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds));
-static int green_clear(PyGreenlet* self);
-static int green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs);
-static int green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context));
-static int green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context));
-static int green_traverse(PyGreenlet* self, visitproc visit, void* arg);
-static void green_dealloc(PyGreenlet* self);
-static PyObject* green_getparent(PyGreenlet* self, void* UNUSED(context));
-
-static int green_is_gc(PyObject* self);
-static PyObject* green_getdead(PyGreenlet* self, void* UNUSED(context));
-static PyObject* green_getrun(PyGreenlet* self, void* UNUSED(context));
-static int green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context));
-static PyObject* green_getframe(PyGreenlet* self, void* UNUSED(context));
-static PyObject* green_repr(PyGreenlet* self);
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp
deleted file mode 100644
index 1b768ee..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- Implementation of the Python slots for PyGreenletUnswitchable_Type
-*/
-#ifndef PY_GREENLET_UNSWITCHABLE_CPP
-#define PY_GREENLET_UNSWITCHABLE_CPP
-
-
-
-#define PY_SSIZE_T_CLEAN
-#include
-#include "structmember.h" // PyMemberDef
-
-#include "greenlet_internal.hpp"
-// Code after this point can assume access to things declared in stdint.h,
-// including the fixed-width types. This goes for the platform-specific switch functions
-// as well.
-#include "greenlet_refs.hpp"
-#include "greenlet_slp_switch.hpp"
-
-#include "greenlet_thread_support.hpp"
-#include "TGreenlet.hpp"
-
-#include "TGreenlet.cpp"
-#include "TGreenletGlobals.cpp"
-#include "TThreadStateDestroy.cpp"
-
-
-using greenlet::LockGuard;
-using greenlet::LockInitError;
-using greenlet::PyErrOccurred;
-using greenlet::Require;
-
-using greenlet::g_handle_exit;
-using greenlet::single_result;
-
-using greenlet::Greenlet;
-using greenlet::UserGreenlet;
-using greenlet::MainGreenlet;
-using greenlet::BrokenGreenlet;
-using greenlet::ThreadState;
-using greenlet::PythonState;
-
-
-#include "PyGreenlet.hpp"
-
-static PyGreenlet*
-green_unswitchable_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
-{
- PyGreenlet* o =
- (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
- if (o) {
- new BrokenGreenlet(o, GET_THREAD_STATE().state().borrow_current());
- assert(Py_REFCNT(o) == 1);
- }
- return o;
-}
-
-static PyObject*
-green_unswitchable_getforce(PyGreenlet* self, void* UNUSED(context))
-{
- BrokenGreenlet* broken = dynamic_cast(self->pimpl);
- return PyBool_FromLong(broken->_force_switch_error);
-}
-
-static int
-green_unswitchable_setforce(PyGreenlet* self, PyObject* nforce, void* UNUSED(context))
-{
- if (!nforce) {
- PyErr_SetString(
- PyExc_AttributeError,
- "Cannot delete force_switch_error"
- );
- return -1;
- }
- BrokenGreenlet* broken = dynamic_cast(self->pimpl);
- int is_true = PyObject_IsTrue(nforce);
- if (is_true == -1) {
- return -1;
- }
- broken->_force_switch_error = is_true;
- return 0;
-}
-
-static PyObject*
-green_unswitchable_getforceslp(PyGreenlet* self, void* UNUSED(context))
-{
- BrokenGreenlet* broken = dynamic_cast(self->pimpl);
- return PyBool_FromLong(broken->_force_slp_switch_error);
-}
-
-static int
-green_unswitchable_setforceslp(PyGreenlet* self, PyObject* nforce, void* UNUSED(context))
-{
- if (!nforce) {
- PyErr_SetString(
- PyExc_AttributeError,
- "Cannot delete force_slp_switch_error"
- );
- return -1;
- }
- BrokenGreenlet* broken = dynamic_cast(self->pimpl);
- int is_true = PyObject_IsTrue(nforce);
- if (is_true == -1) {
- return -1;
- }
- broken->_force_slp_switch_error = is_true;
- return 0;
-}
-
-static PyGetSetDef green_unswitchable_getsets[] = {
- /* name, getter, setter, doc, closure (context pointer) */
- {
- .name="force_switch_error",
- .get=(getter)green_unswitchable_getforce,
- .set=(setter)green_unswitchable_setforce,
- .doc=NULL
- },
- {
- .name="force_slp_switch_error",
- .get=(getter)green_unswitchable_getforceslp,
- .set=(setter)green_unswitchable_setforceslp,
- .doc=nullptr
- },
- {.name=nullptr}
-};
-
-PyTypeObject PyGreenletUnswitchable_Type = {
- .ob_base=PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name="greenlet._greenlet.UnswitchableGreenlet",
- .tp_dealloc= (destructor)green_dealloc, /* tp_dealloc */
- .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- .tp_doc="Undocumented internal class", /* tp_doc */
- .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */
- .tp_clear=(inquiry)green_clear, /* tp_clear */
-
- .tp_getset=green_unswitchable_getsets, /* tp_getset */
- .tp_base=&PyGreenlet_Type, /* tp_base */
- .tp_init=(initproc)green_init, /* tp_init */
- .tp_alloc=PyType_GenericAlloc, /* tp_alloc */
- .tp_new=(newfunc)green_unswitchable_new, /* tp_new */
- .tp_free=PyObject_GC_Del, /* tp_free */
- .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
-};
-
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/PyModule.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/PyModule.cpp
deleted file mode 100644
index a999dc9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/PyModule.cpp
+++ /dev/null
@@ -1,292 +0,0 @@
-#ifndef PY_MODULE_CPP
-#define PY_MODULE_CPP
-
-#include "greenlet_internal.hpp"
-
-
-#include "TGreenletGlobals.cpp"
-#include "TMainGreenlet.cpp"
-#include "TThreadStateDestroy.cpp"
-
-using greenlet::LockGuard;
-using greenlet::ThreadState;
-
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wunused-function"
-# pragma clang diagnostic ignored "-Wunused-variable"
-#endif
-
-PyDoc_STRVAR(mod_getcurrent_doc,
- "getcurrent() -> greenlet\n"
- "\n"
- "Returns the current greenlet (i.e. the one which called this "
- "function).\n");
-
-static PyObject*
-mod_getcurrent(PyObject* UNUSED(module))
-{
- return GET_THREAD_STATE().state().get_current().relinquish_ownership_o();
-}
-
-PyDoc_STRVAR(mod_settrace_doc,
- "settrace(callback) -> object\n"
- "\n"
- "Sets a new tracing function and returns the previous one.\n");
-static PyObject*
-mod_settrace(PyObject* UNUSED(module), PyObject* args)
-{
- PyArgParseParam tracefunc;
- if (!PyArg_ParseTuple(args, "O", &tracefunc)) {
- return NULL;
- }
- ThreadState& state = GET_THREAD_STATE();
- OwnedObject previous = state.get_tracefunc();
- if (!previous) {
- previous = Py_None;
- }
-
- state.set_tracefunc(tracefunc);
-
- return previous.relinquish_ownership();
-}
-
-PyDoc_STRVAR(mod_gettrace_doc,
- "gettrace() -> object\n"
- "\n"
- "Returns the currently set tracing function, or None.\n");
-
-static PyObject*
-mod_gettrace(PyObject* UNUSED(module))
-{
- OwnedObject tracefunc = GET_THREAD_STATE().state().get_tracefunc();
- if (!tracefunc) {
- tracefunc = Py_None;
- }
- return tracefunc.relinquish_ownership();
-}
-
-
-
-PyDoc_STRVAR(mod_set_thread_local_doc,
- "set_thread_local(key, value) -> None\n"
- "\n"
- "Set a value in the current thread-local dictionary. Debugging only.\n");
-
-static PyObject*
-mod_set_thread_local(PyObject* UNUSED(module), PyObject* args)
-{
- PyArgParseParam key;
- PyArgParseParam value;
- PyObject* result = NULL;
-
- if (PyArg_UnpackTuple(args, "set_thread_local", 2, 2, &key, &value)) {
- if(PyDict_SetItem(
- PyThreadState_GetDict(), // borrow
- key,
- value) == 0 ) {
- // success
- Py_INCREF(Py_None);
- result = Py_None;
- }
- }
- return result;
-}
-
-PyDoc_STRVAR(mod_get_pending_cleanup_count_doc,
- "get_pending_cleanup_count() -> Integer\n"
- "\n"
- "Get the number of greenlet cleanup operations pending. Testing only.\n");
-
-
-static PyObject*
-mod_get_pending_cleanup_count(PyObject* UNUSED(module))
-{
- LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
- return PyLong_FromSize_t(mod_globs->thread_states_to_destroy.size());
-}
-
-PyDoc_STRVAR(mod_get_total_main_greenlets_doc,
- "get_total_main_greenlets() -> Integer\n"
- "\n"
- "Quickly return the number of main greenlets that exist. Testing only.\n");
-
-static PyObject*
-mod_get_total_main_greenlets(PyObject* UNUSED(module))
-{
- return PyLong_FromSize_t(G_TOTAL_MAIN_GREENLETS);
-}
-
-
-
-PyDoc_STRVAR(mod_get_clocks_used_doing_optional_cleanup_doc,
- "get_clocks_used_doing_optional_cleanup() -> Integer\n"
- "\n"
- "Get the number of clock ticks the program has used doing optional "
- "greenlet cleanup.\n"
- "Beginning in greenlet 2.0, greenlet tries to find and dispose of greenlets\n"
- "that leaked after a thread exited. This requires invoking Python's garbage collector,\n"
- "which may have a performance cost proportional to the number of live objects.\n"
- "This function returns the amount of processor time\n"
- "greenlet has used to do this. In programs that run with very large amounts of live\n"
- "objects, this metric can be used to decide whether the cost of doing this cleanup\n"
- "is worth the memory leak being corrected. If not, you can disable the cleanup\n"
- "using ``enable_optional_cleanup(False)``.\n"
- "The units are arbitrary and can only be compared to themselves (similarly to ``time.clock()``);\n"
- "for example, to see how it scales with your heap. You can attempt to convert them into seconds\n"
- "by dividing by the value of CLOCKS_PER_SEC."
- "If cleanup has been disabled, returns None."
- "\n"
- "This is an implementation specific, provisional API. It may be changed or removed\n"
- "in the future.\n"
- ".. versionadded:: 2.0"
- );
-static PyObject*
-mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module))
-{
- std::clock_t clocks = ThreadState::clocks_used_doing_gc();
-
- if (clocks == std::clock_t(-1)) {
- Py_RETURN_NONE;
- }
- // This might not actually work on some implementations; clock_t
- // is an opaque type.
- return PyLong_FromSsize_t(clocks);
-}
-
-PyDoc_STRVAR(mod_enable_optional_cleanup_doc,
- "mod_enable_optional_cleanup(bool) -> None\n"
- "\n"
- "Enable or disable optional cleanup operations.\n"
- "See ``get_clocks_used_doing_optional_cleanup()`` for details.\n"
- );
-static PyObject*
-mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag)
-{
- int is_true = PyObject_IsTrue(flag);
- if (is_true == -1) {
- return nullptr;
- }
-
- if (is_true) {
- std::clock_t clocks = ThreadState::clocks_used_doing_gc();
- // If we already have a value, we don't want to lose it.
- if (clocks == std::clock_t(-1)) {
- ThreadState::set_clocks_used_doing_gc(0);
- }
- }
- else {
- ThreadState::set_clocks_used_doing_gc(std::clock_t(-1));
- }
- Py_RETURN_NONE;
-}
-
-
-
-
-#if !GREENLET_PY313
-PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc,
- "get_tstate_trash_delete_nesting() -> Integer\n"
- "\n"
- "Return the 'trash can' nesting level. Testing only.\n");
-static PyObject*
-mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module))
-{
- PyThreadState* tstate = PyThreadState_GET();
-
-#if GREENLET_PY312
- return PyLong_FromLong(tstate->trash.delete_nesting);
-#else
- return PyLong_FromLong(tstate->trash_delete_nesting);
-#endif
-}
-#endif
-
-
-
-
-static PyMethodDef GreenMethods[] = {
- {
- .ml_name="getcurrent",
- .ml_meth=(PyCFunction)mod_getcurrent,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_getcurrent_doc
- },
- {
- .ml_name="settrace",
- .ml_meth=(PyCFunction)mod_settrace,
- .ml_flags=METH_VARARGS,
- .ml_doc=mod_settrace_doc
- },
- {
- .ml_name="gettrace",
- .ml_meth=(PyCFunction)mod_gettrace,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_gettrace_doc
- },
- {
- .ml_name="set_thread_local",
- .ml_meth=(PyCFunction)mod_set_thread_local,
- .ml_flags=METH_VARARGS,
- .ml_doc=mod_set_thread_local_doc
- },
- {
- .ml_name="get_pending_cleanup_count",
- .ml_meth=(PyCFunction)mod_get_pending_cleanup_count,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_get_pending_cleanup_count_doc
- },
- {
- .ml_name="get_total_main_greenlets",
- .ml_meth=(PyCFunction)mod_get_total_main_greenlets,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_get_total_main_greenlets_doc
- },
- {
- .ml_name="get_clocks_used_doing_optional_cleanup",
- .ml_meth=(PyCFunction)mod_get_clocks_used_doing_optional_cleanup,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_get_clocks_used_doing_optional_cleanup_doc
- },
- {
- .ml_name="enable_optional_cleanup",
- .ml_meth=(PyCFunction)mod_enable_optional_cleanup,
- .ml_flags=METH_O,
- .ml_doc=mod_enable_optional_cleanup_doc
- },
-#if !GREENLET_PY313
- {
- .ml_name="get_tstate_trash_delete_nesting",
- .ml_meth=(PyCFunction)mod_get_tstate_trash_delete_nesting,
- .ml_flags=METH_NOARGS,
- .ml_doc=mod_get_tstate_trash_delete_nesting_doc
- },
-#endif
- {.ml_name=NULL, .ml_meth=NULL} /* Sentinel */
-};
-
-static const char* const copy_on_greentype[] = {
- "getcurrent",
- "error",
- "GreenletExit",
- "settrace",
- "gettrace",
- NULL
-};
-
-static struct PyModuleDef greenlet_module_def = {
- .m_base=PyModuleDef_HEAD_INIT,
- .m_name="greenlet._greenlet",
- .m_doc=NULL,
- .m_size=-1,
- .m_methods=GreenMethods,
-};
-
-
-#endif
-
-#ifdef __clang__
-# pragma clang diagnostic pop
-#elif defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp
deleted file mode 100644
index 7e9ab5b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of greenlet::UserGreenlet.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-
-#include "TGreenlet.hpp"
-
-namespace greenlet {
-
-void* BrokenGreenlet::operator new(size_t UNUSED(count))
-{
- return allocator.allocate(1);
-}
-
-
-void BrokenGreenlet::operator delete(void* ptr)
-{
- return allocator.deallocate(static_cast(ptr),
- 1);
-}
-
-greenlet::PythonAllocator greenlet::BrokenGreenlet::allocator;
-
-bool
-BrokenGreenlet::force_slp_switch_error() const noexcept
-{
- return this->_force_slp_switch_error;
-}
-
-UserGreenlet::switchstack_result_t BrokenGreenlet::g_switchstack(void)
-{
- if (this->_force_switch_error) {
- return switchstack_result_t(-1);
- }
- return UserGreenlet::g_switchstack();
-}
-
-}; //namespace greenlet
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp
deleted file mode 100644
index 08a94ae..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef GREENLET_EXCEPTION_STATE_CPP
-#define GREENLET_EXCEPTION_STATE_CPP
-
-#include
-#include "TGreenlet.hpp"
-
-namespace greenlet {
-
-
-ExceptionState::ExceptionState()
-{
- this->clear();
-}
-
-void ExceptionState::operator<<(const PyThreadState *const tstate) noexcept
-{
- this->exc_info = tstate->exc_info;
- this->exc_state = tstate->exc_state;
-}
-
-void ExceptionState::operator>>(PyThreadState *const tstate) noexcept
-{
- tstate->exc_state = this->exc_state;
- tstate->exc_info =
- this->exc_info ? this->exc_info : &tstate->exc_state;
- this->clear();
-}
-
-void ExceptionState::clear() noexcept
-{
- this->exc_info = nullptr;
- this->exc_state.exc_value = nullptr;
-#if !GREENLET_PY311
- this->exc_state.exc_type = nullptr;
- this->exc_state.exc_traceback = nullptr;
-#endif
- this->exc_state.previous_item = nullptr;
-}
-
-int ExceptionState::tp_traverse(visitproc visit, void* arg) noexcept
-{
- Py_VISIT(this->exc_state.exc_value);
-#if !GREENLET_PY311
- Py_VISIT(this->exc_state.exc_type);
- Py_VISIT(this->exc_state.exc_traceback);
-#endif
- return 0;
-}
-
-void ExceptionState::tp_clear() noexcept
-{
- Py_CLEAR(this->exc_state.exc_value);
-#if !GREENLET_PY311
- Py_CLEAR(this->exc_state.exc_type);
- Py_CLEAR(this->exc_state.exc_traceback);
-#endif
-}
-
-
-}; // namespace greenlet
-
-#endif // GREENLET_EXCEPTION_STATE_CPP
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp
deleted file mode 100644
index 1fb056e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp
+++ /dev/null
@@ -1,725 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of greenlet::Greenlet.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#ifndef TGREENLET_CPP
-#define TGREENLET_CPP
-#include "greenlet_internal.hpp"
-#include "TGreenlet.hpp"
-
-
-#include "TGreenletGlobals.cpp"
-#include "TThreadStateDestroy.cpp"
-
-namespace greenlet {
-
-Greenlet::Greenlet(PyGreenlet* p)
- : Greenlet(p, StackState())
-{
-}
-
-Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack)
- : _self(p), stack_state(initial_stack)
-{
- assert(p->pimpl == nullptr);
- p->pimpl = this;
-}
-
-Greenlet::~Greenlet()
-{
- // XXX: Can't do this. tp_clear is a virtual function, and by the
- // time we're here, we've sliced off our child classes.
- //this->tp_clear();
- this->_self->pimpl = nullptr;
-}
-
-bool
-Greenlet::force_slp_switch_error() const noexcept
-{
- return false;
-}
-
-void
-Greenlet::release_args()
-{
- this->switch_args.CLEAR();
-}
-
-/**
- * CAUTION: This will allocate memory and may trigger garbage
- * collection and arbitrary Python code.
- */
-OwnedObject
-Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state))
-{
- // If we're killed because we lost all references in the
- // middle of a switch, that's ok. Don't reset the args/kwargs,
- // we still want to pass them to the parent.
- PyErr_SetString(mod_globs->PyExc_GreenletExit,
- "Killing the greenlet because all references have vanished.");
- // To get here it had to have run before
- return this->g_switch();
-}
-
-inline void
-Greenlet::slp_restore_state() noexcept
-{
-#ifdef SLP_BEFORE_RESTORE_STATE
- SLP_BEFORE_RESTORE_STATE();
-#endif
- this->stack_state.copy_heap_to_stack(
- this->thread_state()->borrow_current()->stack_state);
-}
-
-
-inline int
-Greenlet::slp_save_state(char *const stackref) noexcept
-{
- // XXX: This used to happen in the middle, before saving, but
- // after finding the next owner. Does that matter? This is
- // only defined for Sparc/GCC where it flushes register
- // windows to the stack (I think)
-#ifdef SLP_BEFORE_SAVE_STATE
- SLP_BEFORE_SAVE_STATE();
-#endif
- return this->stack_state.copy_stack_to_heap(stackref,
- this->thread_state()->borrow_current()->stack_state);
-}
-
-/**
- * CAUTION: This will allocate memory and may trigger garbage
- * collection and arbitrary Python code.
- */
-OwnedObject
-Greenlet::on_switchstack_or_initialstub_failure(
- Greenlet* target,
- const Greenlet::switchstack_result_t& err,
- const bool target_was_me,
- const bool was_initial_stub)
-{
- // If we get here, either g_initialstub()
- // failed, or g_switchstack() failed. Either one of those
- // cases SHOULD leave us in the original greenlet with a valid stack.
- if (!PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_SystemError,
- was_initial_stub
- ? "Failed to switch stacks into a greenlet for the first time."
- : "Failed to switch stacks into a running greenlet.");
- }
- this->release_args();
-
- if (target && !target_was_me) {
- target->murder_in_place();
- }
-
- assert(!err.the_new_current_greenlet);
- assert(!err.origin_greenlet);
- return OwnedObject();
-
-}
-
-OwnedGreenlet
-Greenlet::g_switchstack_success() noexcept
-{
- PyThreadState* tstate = PyThreadState_GET();
- // restore the saved state
- this->python_state >> tstate;
- this->exception_state >> tstate;
-
- // The thread state hasn't been changed yet.
- ThreadState* thread_state = this->thread_state();
- OwnedGreenlet result(thread_state->get_current());
- thread_state->set_current(this->self());
- //assert(thread_state->borrow_current().borrow() == this->_self);
- return result;
-}
-
-Greenlet::switchstack_result_t
-Greenlet::g_switchstack(void)
-{
- // if any of these assertions fail, it's likely because we
- // switched away and tried to switch back to us. Early stages of
- // switching are not reentrant because we re-use ``this->args()``.
- // Switching away would happen if we trigger a garbage collection
- // (by just using some Python APIs that happen to allocate Python
- // objects) and some garbage had weakref callbacks or __del__ that
- // switches (people don't write code like that by hand, but with
- // gevent it's possible without realizing it)
- assert(this->args() || PyErr_Occurred());
- { /* save state */
- if (this->thread_state()->is_current(this->self())) {
- // Hmm, nothing to do.
- // TODO: Does this bypass trace events that are
- // important?
- return switchstack_result_t(0,
- this, this->thread_state()->borrow_current());
- }
- BorrowedGreenlet current = this->thread_state()->borrow_current();
- PyThreadState* tstate = PyThreadState_GET();
-
- current->python_state << tstate;
- current->exception_state << tstate;
- this->python_state.will_switch_from(tstate);
- switching_thread_state = this;
- current->expose_frames();
- }
- assert(this->args() || PyErr_Occurred());
- // If this is the first switch into a greenlet, this will
- // return twice, once with 1 in the new greenlet, once with 0
- // in the origin.
- int err;
- if (this->force_slp_switch_error()) {
- err = -1;
- }
- else {
- err = slp_switch();
- }
-
- if (err < 0) { /* error */
- // Tested by
- // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running
- //
- // It's not clear if it's worth trying to clean up and
- // continue here. Failing to switch stacks is a big deal which
- // may not be recoverable (who knows what state the stack is in).
- // Also, we've stolen references in preparation for calling
- // ``g_switchstack_success()`` and we don't have a clean
- // mechanism for backing that all out.
- Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt.");
- }
-
- // No stack-based variables are valid anymore.
-
- // But the global is volatile so we can reload it without the
- // compiler caching it from earlier.
- Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this
- switching_thread_state = nullptr;
- // except that no stack variables are valid, we would:
- // assert(this == greenlet_that_switched_in);
-
- // switchstack success is where we restore the exception state,
- // etc. It returns the origin greenlet because its convenient.
-
- OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success();
- assert(greenlet_that_switched_in->args() || PyErr_Occurred());
- return switchstack_result_t(err, greenlet_that_switched_in, origin);
-}
-
-
-inline void
-Greenlet::check_switch_allowed() const
-{
- // TODO: Make this take a parameter of the current greenlet,
- // or current main greenlet, to make the check for
- // cross-thread switching cheaper. Surely somewhere up the
- // call stack we've already accessed the thread local variable.
-
- // We expect to always have a main greenlet now; accessing the thread state
- // created it. However, if we get here and cleanup has already
- // begun because we're a greenlet that was running in a
- // (now dead) thread, these invariants will not hold true. In
- // fact, accessing `this->thread_state` may not even be possible.
-
- // If the thread this greenlet was running in is dead,
- // we'll still have a reference to a main greenlet, but the
- // thread state pointer we have is bogus.
- // TODO: Give the objects an API to determine if they belong
- // to a dead thread.
-
- const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage();
-
- if (!main_greenlet) {
- throw PyErrOccurred(mod_globs->PyExc_GreenletError,
- "cannot switch to a garbage collected greenlet");
- }
-
- if (!main_greenlet->thread_state()) {
- throw PyErrOccurred(mod_globs->PyExc_GreenletError,
- "cannot switch to a different thread (which happens to have exited)");
- }
-
- // The main greenlet we found was from the .parent lineage.
- // That may or may not have any relationship to the main
- // greenlet of the running thread. We can't actually access
- // our this->thread_state members to try to check that,
- // because it could be in the process of getting destroyed,
- // but setting the main_greenlet->thread_state member to NULL
- // may not be visible yet. So we need to check against the
- // current thread state (once the cheaper checks are out of
- // the way)
- const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet();
- if (
- // lineage main greenlet is not this thread's greenlet
- current_main_greenlet != main_greenlet
- || (
- // atteched to some thread
- this->main_greenlet()
- // XXX: Same condition as above. Was this supposed to be
- // this->main_greenlet()?
- && current_main_greenlet != main_greenlet)
- // switching into a known dead thread (XXX: which, if we get here,
- // is bad, because we just accessed the thread state, which is
- // gone!)
- || (!current_main_greenlet->thread_state())) {
- // CAUTION: This may trigger memory allocations, gc, and
- // arbitrary Python code.
- throw PyErrOccurred(
- mod_globs->PyExc_GreenletError,
- "Cannot switch to a different thread\n\tCurrent: %R\n\tExpected: %R",
- current_main_greenlet, main_greenlet);
- }
-}
-
-const OwnedObject
-Greenlet::context() const
-{
- using greenlet::PythonStateContext;
- OwnedObject result;
-
- if (this->is_currently_running_in_some_thread()) {
- /* Currently running greenlet: context is stored in the thread state,
- not the greenlet object. */
- if (GET_THREAD_STATE().state().is_current(this->self())) {
- result = PythonStateContext::context(PyThreadState_GET());
- }
- else {
- throw ValueError(
- "cannot get context of a "
- "greenlet that is running in a different thread");
- }
- }
- else {
- /* Greenlet is not running: just return context. */
- result = this->python_state.context();
- }
- if (!result) {
- result = OwnedObject::None();
- }
- return result;
-}
-
-
-void
-Greenlet::context(BorrowedObject given)
-{
- using greenlet::PythonStateContext;
- if (!given) {
- throw AttributeError("can't delete context attribute");
- }
- if (given.is_None()) {
- /* "Empty context" is stored as NULL, not None. */
- given = nullptr;
- }
-
- //checks type, incrs refcnt
- greenlet::refs::OwnedContext context(given);
- PyThreadState* tstate = PyThreadState_GET();
-
- if (this->is_currently_running_in_some_thread()) {
- if (!GET_THREAD_STATE().state().is_current(this->self())) {
- throw ValueError("cannot set context of a greenlet"
- " that is running in a different thread");
- }
-
- /* Currently running greenlet: context is stored in the thread state,
- not the greenlet object. */
- OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate));
- PythonStateContext::context(tstate, context.relinquish_ownership());
- }
- else {
- /* Greenlet is not running: just set context. Note that the
- greenlet may be dead.*/
- this->python_state.context() = context;
- }
-}
-
-/**
- * CAUTION: May invoke arbitrary Python code.
- *
- * Figure out what the result of ``greenlet.switch(arg, kwargs)``
- * should be and transfers ownership of it to the left-hand-side.
- *
- * If switch() was just passed an arg tuple, then we'll just return that.
- * If only keyword arguments were passed, then we'll pass the keyword
- * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and
- * return both.
- *
- * CAUTION: This may allocate a new tuple object, which may
- * cause the Python garbage collector to run, which in turn may
- * run arbitrary Python code that switches.
- */
-OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept
-{
- // Because this may invoke arbitrary Python code, which could
- // result in switching back to us, we need to get the
- // arguments locally on the stack.
- assert(rhs);
- OwnedObject args = rhs.args();
- OwnedObject kwargs = rhs.kwargs();
- rhs.CLEAR();
- // We shouldn't be called twice for the same switch.
- assert(args || kwargs);
- assert(!rhs);
-
- if (!kwargs) {
- lhs = args;
- }
- else if (!PyDict_Size(kwargs.borrow())) {
- lhs = args;
- }
- else if (!PySequence_Length(args.borrow())) {
- lhs = kwargs;
- }
- else {
- // PyTuple_Pack allocates memory, may GC, may run arbitrary
- // Python code.
- lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow()));
- }
- return lhs;
-}
-
-static OwnedObject
-g_handle_exit(const OwnedObject& greenlet_result)
-{
- if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) {
- /* catch and ignore GreenletExit */
- PyErrFetchParam val;
- PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam());
- if (!val) {
- return OwnedObject::None();
- }
- return OwnedObject(val);
- }
-
- if (greenlet_result) {
- // package the result into a 1-tuple
- // PyTuple_Pack increments the reference of its arguments,
- // so we always need to decref the greenlet result;
- // the owner will do that.
- return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow()));
- }
-
- return OwnedObject();
-}
-
-
-
-/**
- * May run arbitrary Python code.
- */
-OwnedObject
-Greenlet::g_switch_finish(const switchstack_result_t& err)
-{
- assert(err.the_new_current_greenlet == this);
-
- ThreadState& state = *this->thread_state();
- // Because calling the trace function could do arbitrary things,
- // including switching away from this greenlet and then maybe
- // switching back, we need to capture the arguments now so that
- // they don't change.
- OwnedObject result;
- if (this->args()) {
- result <<= this->args();
- }
- else {
- assert(PyErr_Occurred());
- }
- assert(!this->args());
- try {
- // Our only caller handles the bad error case
- assert(err.status >= 0);
- assert(state.borrow_current() == this->self());
- if (OwnedObject tracefunc = state.get_tracefunc()) {
- assert(result || PyErr_Occurred());
- g_calltrace(tracefunc,
- result ? mod_globs->event_switch : mod_globs->event_throw,
- err.origin_greenlet,
- this->self());
- }
- // The above could have invoked arbitrary Python code, but
- // it couldn't switch back to this object and *also*
- // throw an exception, so the args won't have changed.
-
- if (PyErr_Occurred()) {
- // We get here if we fell of the end of the run() function
- // raising an exception. The switch itself was
- // successful, but the function raised.
- // valgrind reports that memory allocated here can still
- // be reached after a test run.
- throw PyErrOccurred::from_current();
- }
- return result;
- }
- catch (const PyErrOccurred&) {
- /* Turn switch errors into switch throws */
- /* Turn trace errors into switch throws */
- this->release_args();
- throw;
- }
-}
-
-void
-Greenlet::g_calltrace(const OwnedObject& tracefunc,
- const greenlet::refs::ImmortalEventName& event,
- const BorrowedGreenlet& origin,
- const BorrowedGreenlet& target)
-{
- PyErrPieces saved_exc;
- try {
- TracingGuard tracing_guard;
- // TODO: We have saved the active exception (if any) that's
- // about to be raised. In the 'throw' case, we could provide
- // the exception to the tracefunction, which seems very helpful.
- tracing_guard.CallTraceFunction(tracefunc, event, origin, target);
- }
- catch (const PyErrOccurred&) {
- // In case of exceptions trace function is removed,
- // and any existing exception is replaced with the tracing
- // exception.
- GET_THREAD_STATE().state().set_tracefunc(Py_None);
- throw;
- }
-
- saved_exc.PyErrRestore();
- assert(
- (event == mod_globs->event_throw && PyErr_Occurred())
- || (event == mod_globs->event_switch && !PyErr_Occurred())
- );
-}
-
-void
-Greenlet::murder_in_place()
-{
- if (this->active()) {
- assert(!this->is_currently_running_in_some_thread());
- this->deactivate_and_free();
- }
-}
-
-inline void
-Greenlet::deactivate_and_free()
-{
- if (!this->active()) {
- return;
- }
- // Throw away any saved stack.
- this->stack_state = StackState();
- assert(!this->stack_state.active());
- // Throw away any Python references.
- // We're holding a borrowed reference to the last
- // frame we executed. Since we borrowed it, the
- // normal traversal, clear, and dealloc functions
- // ignore it, meaning it leaks. (The thread state
- // object can't find it to clear it when that's
- // deallocated either, because by definition if we
- // got an object on this list, it wasn't
- // running and the thread state doesn't have
- // this frame.)
- // So here, we *do* clear it.
- this->python_state.tp_clear(true);
-}
-
-bool
-Greenlet::belongs_to_thread(const ThreadState* thread_state) const
-{
- if (!this->thread_state() // not running anywhere, or thread
- // exited
- || !thread_state) { // same, or there is no thread state.
- return false;
- }
- return true;
-}
-
-
-void
-Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state)
-{
- /* Cannot raise an exception to kill the greenlet if
- it is not running in the same thread! */
- if (this->belongs_to_thread(current_thread_state)) {
- assert(current_thread_state);
- // To get here it had to have run before
- /* Send the greenlet a GreenletExit exception. */
-
- // We don't care about the return value, only whether an
- // exception happened.
- this->throw_GreenletExit_during_dealloc(*current_thread_state);
- return;
- }
-
- // Not the same thread! Temporarily save the greenlet
- // into its thread's deleteme list, *if* it exists.
- // If that thread has already exited, and processed its pending
- // cleanup, we'll never be able to clean everything up: we won't
- // be able to raise an exception.
- // That's mostly OK! Since we can't add it to a list, our refcount
- // won't increase, and we'll go ahead with the DECREFs later.
-
- ThreadState *const thread_state = this->thread_state();
- if (thread_state) {
- thread_state->delete_when_thread_running(this->self());
- }
- else {
- // The thread is dead, we can't raise an exception.
- // We need to make it look non-active, though, so that dealloc
- // finishes killing it.
- this->deactivate_and_free();
- }
- return;
-}
-
-
-int
-Greenlet::tp_traverse(visitproc visit, void* arg)
-{
-
- int result;
- if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) {
- return result;
- }
- //XXX: This is ugly. But so is handling everything having to do
- //with the top frame.
- bool visit_top_frame = this->was_running_in_dead_thread();
- // When true, the thread is dead. Our implicit weak reference to the
- // frame is now all that's left; we consider ourselves to
- // strongly own it now.
- if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) {
- return result;
- }
- return 0;
-}
-
-int
-Greenlet::tp_clear()
-{
- bool own_top_frame = this->was_running_in_dead_thread();
- this->exception_state.tp_clear();
- this->python_state.tp_clear(own_top_frame);
- return 0;
-}
-
-bool Greenlet::is_currently_running_in_some_thread() const
-{
- return this->stack_state.active() && !this->python_state.top_frame();
-}
-
-#if GREENLET_PY312
-void GREENLET_NOINLINE(Greenlet::expose_frames)()
-{
- if (!this->python_state.top_frame()) {
- return;
- }
-
- _PyInterpreterFrame* last_complete_iframe = nullptr;
- _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame;
- while (iframe) {
- // We must make a copy before looking at the iframe contents,
- // since iframe might point to a portion of the greenlet's C stack
- // that was spilled when switching greenlets.
- _PyInterpreterFrame iframe_copy;
- this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe));
- if (!_PyFrame_IsIncomplete(&iframe_copy)) {
- // If the iframe were OWNED_BY_CSTACK then it would always be
- // incomplete. Since it's not incomplete, it's not on the C stack
- // and we can access it through the original `iframe` pointer
- // directly. This is important since GetFrameObject might
- // lazily _create_ the frame object and we don't want the
- // interpreter to lose track of it.
- //
- #if !GREENLET_PY315
- // This enum value was removed in
- // https://github.com/python/cpython/pull/141108
-
- assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK);
- #endif
-
- // We really want to just write:
- // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe);
- // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject
- // which is not a visible symbol in libpython. The easiest
- // way to get a public function to call it is using
- // PyFrame_GetBack, which is defined as follows:
- // assert(frame != NULL);
- // assert(!_PyFrame_IsIncomplete(frame->f_frame));
- // PyFrameObject *back = frame->f_back;
- // if (back == NULL) {
- // _PyInterpreterFrame *prev = frame->f_frame->previous;
- // prev = _PyFrame_GetFirstComplete(prev);
- // if (prev) {
- // back = _PyFrame_GetFrameObject(prev);
- // }
- // }
- // return (PyFrameObject*)Py_XNewRef(back);
- if (!iframe->frame_obj) {
- PyFrameObject dummy_frame;
- _PyInterpreterFrame dummy_iframe;
- dummy_frame.f_back = nullptr;
- dummy_frame.f_frame = &dummy_iframe;
- // force the iframe to be considered complete without
- // needing to check its code object:
- dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR;
- dummy_iframe.previous = iframe;
- assert(!_PyFrame_IsIncomplete(&dummy_iframe));
- // Drop the returned reference immediately; the iframe
- // continues to hold a strong reference
- Py_XDECREF(PyFrame_GetBack(&dummy_frame));
- assert(iframe->frame_obj);
- }
-
- // This is a complete frame, so make the last one of those we saw
- // point at it, bypassing any incomplete frames (which may have
- // been on the C stack) in between the two. We're overwriting
- // last_complete_iframe->previous and need that to be reversible,
- // so we store the original previous ptr in the frame object
- // (which we must have created on a previous iteration through
- // this loop). The frame object has a bunch of storage that is
- // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only
- // occurs when the frame object outlives the frame's execution,
- // which can't have happened yet because the frame is currently
- // executing as far as the interpreter is concerned. So, we can
- // reuse it for our own purposes.
- assert(iframe->owner == FRAME_OWNED_BY_THREAD
- || iframe->owner == FRAME_OWNED_BY_GENERATOR);
- if (last_complete_iframe) {
- assert(last_complete_iframe->frame_obj);
- memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
- &last_complete_iframe->previous, sizeof(void *));
- last_complete_iframe->previous = iframe;
- }
- last_complete_iframe = iframe;
- }
- // Frames that are OWNED_BY_FRAME_OBJECT are linked via the
- // frame's f_back while all others are linked via the iframe's
- // previous ptr. Since all the frames we traverse are running
- // as far as the interpreter is concerned, we don't have to
- // worry about the OWNED_BY_FRAME_OBJECT case.
- iframe = iframe_copy.previous;
- }
-
- // Give the outermost complete iframe a null previous pointer to
- // account for any potential incomplete/C-stack iframes between it
- // and the actual top-of-stack
- if (last_complete_iframe) {
- assert(last_complete_iframe->frame_obj);
- memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
- &last_complete_iframe->previous, sizeof(void *));
- last_complete_iframe->previous = nullptr;
- }
-}
-#else
-void Greenlet::expose_frames()
-{
-
-}
-#endif
-
-}; // namespace greenlet
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp
deleted file mode 100644
index 32330e9..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp
+++ /dev/null
@@ -1,837 +0,0 @@
-#ifndef GREENLET_GREENLET_HPP
-#define GREENLET_GREENLET_HPP
-/*
- * Declarations of the core data structures.
-*/
-
-#define PY_SSIZE_T_CLEAN
-#include
-
-#include "greenlet_compiler_compat.hpp"
-#include "greenlet_refs.hpp"
-#include "greenlet_cpython_compat.hpp"
-#include "greenlet_allocator.hpp"
-
-using greenlet::refs::OwnedObject;
-using greenlet::refs::OwnedGreenlet;
-using greenlet::refs::OwnedMainGreenlet;
-using greenlet::refs::BorrowedGreenlet;
-
-#if PY_VERSION_HEX < 0x30B00A6
-# define _PyCFrame CFrame
-# define _PyInterpreterFrame _interpreter_frame
-#endif
-
-#if GREENLET_PY312
-# define Py_BUILD_CORE
-# include "internal/pycore_frame.h"
-#endif
-
-#if GREENLET_PY314
-# include "internal/pycore_interpframe_structs.h"
-#if defined(_MSC_VER) || defined(__MINGW64__)
-# include "greenlet_msvc_compat.hpp"
-#else
-# include "internal/pycore_interpframe.h"
-#endif
-#ifdef Py_GIL_DISABLED
-# include "internal/pycore_tstate.h"
-#endif
-#endif
-
-// XXX: TODO: Work to remove all virtual functions
-// for speed of calling and size of objects (no vtable).
-// One pattern is the Curiously Recurring Template
-namespace greenlet
-{
- class ExceptionState
- {
- private:
- G_NO_COPIES_OF_CLS(ExceptionState);
-
- // Even though these are borrowed objects, we actually own
- // them, when they're not null.
- // XXX: Express that in the API.
- private:
- _PyErr_StackItem* exc_info;
- _PyErr_StackItem exc_state;
- public:
- ExceptionState();
- void operator<<(const PyThreadState *const tstate) noexcept;
- void operator>>(PyThreadState* tstate) noexcept;
- void clear() noexcept;
-
- int tp_traverse(visitproc visit, void* arg) noexcept;
- void tp_clear() noexcept;
- };
-
- template
- void operator<<(const PyThreadState *const tstate, T& exc);
-
- class PythonStateContext
- {
- protected:
- greenlet::refs::OwnedContext _context;
- public:
- inline const greenlet::refs::OwnedContext& context() const
- {
- return this->_context;
- }
- inline greenlet::refs::OwnedContext& context()
- {
- return this->_context;
- }
-
- inline void tp_clear()
- {
- this->_context.CLEAR();
- }
-
- template
- inline static PyObject* context(T* tstate)
- {
- return tstate->context;
- }
-
- template
- inline static void context(T* tstate, PyObject* new_context)
- {
- tstate->context = new_context;
- tstate->context_ver++;
- }
- };
- class SwitchingArgs;
- class PythonState : public PythonStateContext
- {
- public:
- typedef greenlet::refs::OwnedReference OwnedFrame;
- private:
- G_NO_COPIES_OF_CLS(PythonState);
- // We own this if we're suspended (although currently we don't
- // tp_traverse into it; that's a TODO). If we're running, it's
- // empty. If we get deallocated and *still* have a frame, it
- // won't be reachable from the place that normally decref's
- // it, so we need to do it (hence owning it).
- OwnedFrame _top_frame;
-#if GREENLET_USE_CFRAME
- _PyCFrame* cframe;
- int use_tracing;
-#endif
-#if GREENLET_PY314
- int py_recursion_depth;
- // I think this is only used by the JIT. At least,
- // we only got errors not switching it when the JIT was enabled.
- // Python/generated_cases.c.h:12469: _PyEval_EvalFrameDefault:
- // Assertion `tstate->current_executor == NULL' failed.
- // see https://github.com/python-greenlet/greenlet/issues/460
- PyObject* current_executor;
- _PyStackRef* stackpointer;
- #ifdef Py_GIL_DISABLED
- _PyCStackRef* c_stack_refs;
- #endif
-#elif GREENLET_PY312
- int py_recursion_depth;
- int c_recursion_depth;
-#else
- int recursion_depth;
-#endif
-#if GREENLET_PY313
- PyObject *delete_later;
-#else
- int trash_delete_nesting;
-#endif
-#if GREENLET_PY311
- _PyInterpreterFrame* current_frame;
- _PyStackChunk* datastack_chunk;
- PyObject** datastack_top;
- PyObject** datastack_limit;
-#endif
- // The PyInterpreterFrame list on 3.12+ contains some entries that are
- // on the C stack, which can't be directly accessed while a greenlet is
- // suspended. In order to keep greenlet gr_frame introspection working,
- // we adjust stack switching to rewrite the interpreter frame list
- // to skip these C-stack frames; we call this "exposing" the greenlet's
- // frames because it makes them valid to work with in Python. Then when
- // the greenlet is resumed we need to remember to reverse the operation
- // we did. The C-stack frames are "entry frames" which are a low-level
- // interpreter detail; they're not needed for introspection, but do
- // need to be present for the eval loop to work.
- void unexpose_frames();
-
- public:
-
- PythonState();
- // You can use this for testing whether we have a frame
- // or not. It returns const so they can't modify it.
- const OwnedFrame& top_frame() const noexcept;
-
- inline void operator<<(const PyThreadState *const tstate) noexcept;
- inline void operator>>(PyThreadState* tstate) noexcept;
- void clear() noexcept;
-
- int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept;
- void tp_clear(bool own_top_frame) noexcept;
- void set_initial_state(const PyThreadState* const tstate) noexcept;
-#if GREENLET_USE_CFRAME
- void set_new_cframe(_PyCFrame& frame) noexcept;
-#endif
-
- void may_switch_away() noexcept;
- inline void will_switch_from(PyThreadState *const origin_tstate) noexcept;
- void did_finish(PyThreadState* tstate) noexcept;
- };
-
- class StackState
- {
- // By having only plain C (POD) members, no virtual functions
- // or bases, we get a trivial assignment operator generated
- // for us. However, that's not safe since we do manage memory.
- // So we declare an assignment operator that only works if we
- // don't have any memory allocated. (We don't use
- // std::shared_ptr for reference counting just to keep this
- // object small)
- private:
- char* _stack_start;
- char* stack_stop;
- char* stack_copy;
- intptr_t _stack_saved;
- StackState* stack_prev;
- inline int copy_stack_to_heap_up_to(const char* const stop) noexcept;
- inline void free_stack_copy() noexcept;
-
- public:
- /**
- * Creates a started, but inactive, state, using *current*
- * as the previous.
- */
- StackState(void* mark, StackState& current);
- /**
- * Creates an inactive, unstarted, state.
- */
- StackState();
- ~StackState();
- StackState(const StackState& other);
- StackState& operator=(const StackState& other);
- inline void copy_heap_to_stack(const StackState& current) noexcept;
- inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept;
- inline bool started() const noexcept;
- inline bool main() const noexcept;
- inline bool active() const noexcept;
- inline void set_active() noexcept;
- inline void set_inactive() noexcept;
- inline intptr_t stack_saved() const noexcept;
- inline char* stack_start() const noexcept;
- static inline StackState make_main() noexcept;
-#ifdef GREENLET_USE_STDIO
- friend std::ostream& operator<<(std::ostream& os, const StackState& s);
-#endif
-
- // Fill in [dest, dest + n) with the values that would be at
- // [src, src + n) while this greenlet is running. This is like memcpy
- // except that if the greenlet is suspended it accounts for the portion
- // of the greenlet's stack that was spilled to the heap. `src` may
- // be on this greenlet's stack, or on the heap, but not on a different
- // greenlet's stack.
- void copy_from_stack(void* dest, const void* src, size_t n) const;
- };
-#ifdef GREENLET_USE_STDIO
- std::ostream& operator<<(std::ostream& os, const StackState& s);
-#endif
-
- class SwitchingArgs
- {
- private:
- G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs);
- // If args and kwargs are both false (NULL), this is a *throw*, not a
- // switch. PyErr_... must have been called already.
- OwnedObject _args;
- OwnedObject _kwargs;
- public:
-
- SwitchingArgs()
- {}
-
- SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs)
- : _args(args),
- _kwargs(kwargs)
- {}
-
- SwitchingArgs(const SwitchingArgs& other)
- : _args(other._args),
- _kwargs(other._kwargs)
- {}
-
- const OwnedObject& args()
- {
- return this->_args;
- }
-
- const OwnedObject& kwargs()
- {
- return this->_kwargs;
- }
-
- /**
- * Moves ownership from the argument to this object.
- */
- SwitchingArgs& operator<<=(SwitchingArgs& other)
- {
- if (this != &other) {
- this->_args = other._args;
- this->_kwargs = other._kwargs;
- other.CLEAR();
- }
- return *this;
- }
-
- /**
- * Acquires ownership of the argument (consumes the reference).
- */
- SwitchingArgs& operator<<=(PyObject* args)
- {
- this->_args = OwnedObject::consuming(args);
- this->_kwargs.CLEAR();
- return *this;
- }
-
- /**
- * Acquires ownership of the argument.
- *
- * Sets the args to be the given value; clears the kwargs.
- */
- SwitchingArgs& operator<<=(OwnedObject& args)
- {
- assert(&args != &this->_args);
- this->_args = args;
- this->_kwargs.CLEAR();
- args.CLEAR();
-
- return *this;
- }
-
- explicit operator bool() const noexcept
- {
- return this->_args || this->_kwargs;
- }
-
- inline void CLEAR()
- {
- this->_args.CLEAR();
- this->_kwargs.CLEAR();
- }
-
- const std::string as_str() const noexcept
- {
- return PyUnicode_AsUTF8(
- OwnedObject::consuming(
- PyUnicode_FromFormat(
- "SwitchingArgs(args=%R, kwargs=%R)",
- this->_args.borrow(),
- this->_kwargs.borrow()
- )
- ).borrow()
- );
- }
- };
-
- class ThreadState;
-
- class UserGreenlet;
- class MainGreenlet;
-
- class Greenlet
- {
- private:
- G_NO_COPIES_OF_CLS(Greenlet);
- PyGreenlet* const _self;
- private:
- // XXX: Work to remove these.
- friend class ThreadState;
- friend class UserGreenlet;
- friend class MainGreenlet;
- protected:
- ExceptionState exception_state;
- SwitchingArgs switch_args;
- StackState stack_state;
- PythonState python_state;
- Greenlet(PyGreenlet* p, const StackState& initial_state);
- public:
- // This constructor takes ownership of the PyGreenlet, by
- // setting ``p->pimpl = this;``.
- Greenlet(PyGreenlet* p);
- virtual ~Greenlet();
-
- const OwnedObject context() const;
-
- // You MUST call this _very_ early in the switching process to
- // prepare anything that may need prepared. This might perform
- // garbage collections or otherwise run arbitrary Python code.
- //
- // One specific use of it is for Python 3.11+, preventing
- // running arbitrary code at unsafe times. See
- // PythonState::may_switch_away().
- inline void may_switch_away()
- {
- this->python_state.may_switch_away();
- }
-
- inline void context(refs::BorrowedObject new_context);
-
- inline SwitchingArgs& args()
- {
- return this->switch_args;
- }
-
- virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0;
-
- inline intptr_t stack_saved() const noexcept
- {
- return this->stack_state.stack_saved();
- }
-
- // This is used by the macro SLP_SAVE_STATE to compute the
- // difference in stack sizes. It might be nice to handle the
- // computation ourself, but the type of the result
- // varies by platform, so doing it in the macro is the
- // simplest way.
- inline const char* stack_start() const noexcept
- {
- return this->stack_state.stack_start();
- }
-
- virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
- virtual OwnedObject g_switch() = 0;
- /**
- * Force the greenlet to appear dead. Used when it's not
- * possible to throw an exception into a greenlet anymore.
- *
- * This losses access to the thread state and the main greenlet.
- */
- virtual void murder_in_place();
-
- /**
- * Called when somebody notices we were running in a dead
- * thread to allow cleaning up resources (because we can't
- * raise GreenletExit into it anymore).
- * This is very similar to ``murder_in_place()``, except that
- * it DOES NOT lose the main greenlet or thread state.
- */
- inline void deactivate_and_free();
-
-
- // Called when some thread wants to deallocate a greenlet
- // object.
- // The thread may or may not be the same thread the greenlet
- // was running in.
- // The thread state will be null if the thread the greenlet
- // was running in was known to have exited.
- void deallocing_greenlet_in_thread(const ThreadState* current_state);
-
- // Must be called on 3.12+ before exposing a suspended greenlet's
- // frames to user code. This rewrites the linked list of interpreter
- // frames to skip the ones that are being stored on the C stack (which
- // can't be safely accessed while the greenlet is suspended because
- // that stack space might be hosting a different greenlet), and
- // sets PythonState::frames_were_exposed so we remember to restore
- // the original list before resuming the greenlet. The C-stack frames
- // are a low-level interpreter implementation detail; while they're
- // important to the bytecode eval loop, they're superfluous for
- // introspection purposes.
- void expose_frames();
-
-
- // TODO: Figure out how to make these non-public.
- inline void slp_restore_state() noexcept;
- inline int slp_save_state(char *const stackref) noexcept;
-
- inline bool is_currently_running_in_some_thread() const;
- virtual bool belongs_to_thread(const ThreadState* state) const;
-
- inline bool started() const
- {
- return this->stack_state.started();
- }
- inline bool active() const
- {
- return this->stack_state.active();
- }
- inline bool main() const
- {
- return this->stack_state.main();
- }
- virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0;
-
- virtual const OwnedGreenlet parent() const = 0;
- virtual void parent(const refs::BorrowedObject new_parent) = 0;
-
- inline const PythonState::OwnedFrame& top_frame()
- {
- return this->python_state.top_frame();
- }
-
- virtual const OwnedObject& run() const = 0;
- virtual void run(const refs::BorrowedObject nrun) = 0;
-
-
- virtual int tp_traverse(visitproc visit, void* arg);
- virtual int tp_clear();
-
-
- // Return the thread state that the greenlet is running in, or
- // null if the greenlet is not running or the thread is known
- // to have exited.
- virtual ThreadState* thread_state() const noexcept = 0;
-
- // Return true if the greenlet is known to have been running
- // (active) in a thread that has now exited.
- virtual bool was_running_in_dead_thread() const noexcept = 0;
-
- // Return a borrowed greenlet that is the Python object
- // this object represents.
- inline BorrowedGreenlet self() const noexcept
- {
- return BorrowedGreenlet(this->_self);
- }
-
- // For testing. If this returns true, we should pretend that
- // slp_switch() failed.
- virtual bool force_slp_switch_error() const noexcept;
-
- protected:
- inline void release_args();
-
- // The functions that must not be inlined are declared virtual.
- // We also mark them as protected, not private, so that the
- // compiler is forced to call them through a function pointer.
- // (A sufficiently smart compiler could directly call a private
- // virtual function since it can never be overridden in a
- // subclass).
-
- // Also TODO: Switch away from integer error codes and to enums,
- // or throw exceptions when possible.
- struct switchstack_result_t
- {
- int status;
- Greenlet* the_new_current_greenlet;
- OwnedGreenlet origin_greenlet;
-
- switchstack_result_t()
- : status(0),
- the_new_current_greenlet(nullptr)
- {}
-
- switchstack_result_t(int err)
- : status(err),
- the_new_current_greenlet(nullptr)
- {}
-
- switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin)
- : status(err),
- the_new_current_greenlet(state),
- origin_greenlet(origin)
- {
- }
-
- switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin)
- : status(err),
- the_new_current_greenlet(state),
- origin_greenlet(origin)
- {
- }
-
- switchstack_result_t(const switchstack_result_t& other)
- : status(other.status),
- the_new_current_greenlet(other.the_new_current_greenlet),
- origin_greenlet(other.origin_greenlet)
- {}
-
- switchstack_result_t& operator=(const switchstack_result_t& other)
- {
- this->status = other.status;
- this->the_new_current_greenlet = other.the_new_current_greenlet;
- this->origin_greenlet = other.origin_greenlet;
- return *this;
- }
- };
-
- OwnedObject on_switchstack_or_initialstub_failure(
- Greenlet* target,
- const switchstack_result_t& err,
- const bool target_was_me=false,
- const bool was_initial_stub=false);
-
- // Returns the previous greenlet we just switched away from.
- virtual OwnedGreenlet g_switchstack_success() noexcept;
-
-
- // Check the preconditions for switching to this greenlet; if they
- // aren't met, throws PyErrOccurred. Most callers will want to
- // catch this and clear the arguments
- inline void check_switch_allowed() const;
- class GreenletStartedWhileInPython : public std::runtime_error
- {
- public:
- GreenletStartedWhileInPython() : std::runtime_error("")
- {}
- };
-
- protected:
-
-
- /**
- Perform a stack switch into this greenlet.
-
- This temporarily sets the global variable
- ``switching_thread_state`` to this greenlet; as soon as the
- call to ``slp_switch`` completes, this is reset to NULL.
- Consequently, this depends on the GIL.
-
- TODO: Adopt the stackman model and pass ``slp_switch`` a
- callback function and context pointer; this eliminates the
- need for global variables altogether.
-
- Because the stack switch happens in this function, this
- function can't use its own stack (local) variables, set
- before the switch, and then accessed after the switch.
-
- Further, you con't even access ``g_thread_state_global``
- before and after the switch from the global variable.
- Because it is thread local some compilers cache it in a
- register/on the stack, notably new versions of MSVC; this
- breaks with strange crashes sometime later, because writing
- to anything in ``g_thread_state_global`` after the switch
- is actually writing to random memory. For this reason, we
- call a non-inlined function to finish the operation. (XXX:
- The ``/GT`` MSVC compiler argument probably fixes that.)
-
- It is very important that stack switch is 'atomic', i.e. no
- calls into other Python code allowed (except very few that
- are safe), because global variables are very fragile. (This
- should no longer be the case with thread-local variables.)
-
- */
- // Made virtual to facilitate subclassing UserGreenlet for testing.
- virtual switchstack_result_t g_switchstack(void);
-
-class TracingGuard
-{
-private:
- PyThreadState* tstate;
-public:
- TracingGuard()
- : tstate(PyThreadState_GET())
- {
- PyThreadState_EnterTracing(this->tstate);
- }
-
- ~TracingGuard()
- {
- PyThreadState_LeaveTracing(this->tstate);
- this->tstate = nullptr;
- }
-
- inline void CallTraceFunction(const OwnedObject& tracefunc,
- const greenlet::refs::ImmortalEventName& event,
- const BorrowedGreenlet& origin,
- const BorrowedGreenlet& target)
- {
- // TODO: This calls tracefunc(event, (origin, target)). Add a shortcut
- // function for that that's specialized to avoid the Py_BuildValue
- // string parsing, or start with just using "ON" format with PyTuple_Pack(2,
- // origin, target). That seems like what the N format is meant
- // for.
- // XXX: Why does event not automatically cast back to a PyObject?
- // It tries to call the "deleted constructor ImmortalEventName
- // const" instead.
- assert(tracefunc);
- assert(event);
- assert(origin);
- assert(target);
- greenlet::refs::NewReference retval(
- PyObject_CallFunction(
- tracefunc.borrow(),
- "O(OO)",
- event.borrow(),
- origin.borrow(),
- target.borrow()
- ));
- if (!retval) {
- throw PyErrOccurred::from_current();
- }
- }
-};
-
- static void
- g_calltrace(const OwnedObject& tracefunc,
- const greenlet::refs::ImmortalEventName& event,
- const greenlet::refs::BorrowedGreenlet& origin,
- const BorrowedGreenlet& target);
- private:
- OwnedObject g_switch_finish(const switchstack_result_t& err);
-
- };
-
- class UserGreenlet : public Greenlet
- {
- private:
- static greenlet::PythonAllocator allocator;
- OwnedMainGreenlet _main_greenlet;
- OwnedObject _run_callable;
- OwnedGreenlet _parent;
- public:
- static void* operator new(size_t UNUSED(count));
- static void operator delete(void* ptr);
-
- UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent);
- virtual ~UserGreenlet();
-
- virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
- virtual bool was_running_in_dead_thread() const noexcept;
- virtual ThreadState* thread_state() const noexcept;
- virtual OwnedObject g_switch();
- virtual const OwnedObject& run() const
- {
- if (this->started() || !this->_run_callable) {
- throw AttributeError("run");
- }
- return this->_run_callable;
- }
- virtual void run(const refs::BorrowedObject nrun);
-
- virtual const OwnedGreenlet parent() const;
- virtual void parent(const refs::BorrowedObject new_parent);
-
- virtual const refs::BorrowedMainGreenlet main_greenlet() const;
-
- virtual void murder_in_place();
- virtual bool belongs_to_thread(const ThreadState* state) const;
- virtual int tp_traverse(visitproc visit, void* arg);
- virtual int tp_clear();
- class ParentIsCurrentGuard
- {
- private:
- OwnedGreenlet oldparent;
- UserGreenlet* greenlet;
- G_NO_COPIES_OF_CLS(ParentIsCurrentGuard);
- public:
- ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state);
- ~ParentIsCurrentGuard();
- };
- virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
- protected:
- virtual switchstack_result_t g_initialstub(void* mark);
- private:
- // This function isn't meant to return.
- // This accepts raw pointers and the ownership of them at the
- // same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``.
- void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run);
- };
-
- class BrokenGreenlet : public UserGreenlet
- {
- private:
- static greenlet::PythonAllocator allocator;
- public:
- bool _force_switch_error = false;
- bool _force_slp_switch_error = false;
-
- static void* operator new(size_t UNUSED(count));
- static void operator delete(void* ptr);
- BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent)
- : UserGreenlet(p, the_parent)
- {}
- virtual ~BrokenGreenlet()
- {}
-
- virtual switchstack_result_t g_switchstack(void);
- virtual bool force_slp_switch_error() const noexcept;
-
- };
-
- class MainGreenlet : public Greenlet
- {
- private:
- static greenlet::PythonAllocator allocator;
- refs::BorrowedMainGreenlet _self;
- ThreadState* _thread_state;
- G_NO_COPIES_OF_CLS(MainGreenlet);
- public:
- static void* operator new(size_t UNUSED(count));
- static void operator delete(void* ptr);
-
- MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*);
- virtual ~MainGreenlet();
-
-
- virtual const OwnedObject& run() const;
- virtual void run(const refs::BorrowedObject nrun);
-
- virtual const OwnedGreenlet parent() const;
- virtual void parent(const refs::BorrowedObject new_parent);
-
- virtual const refs::BorrowedMainGreenlet main_greenlet() const;
-
- virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
- virtual bool was_running_in_dead_thread() const noexcept;
- virtual ThreadState* thread_state() const noexcept;
- void thread_state(ThreadState*) noexcept;
- virtual OwnedObject g_switch();
- virtual int tp_traverse(visitproc visit, void* arg);
- };
-
- // Instantiate one on the stack to save the GC state,
- // and then disable GC. When it goes out of scope, GC will be
- // restored to its original state. Sadly, these APIs are only
- // available on 3.10+; luckily, we only need them on 3.11+.
-#if GREENLET_PY310
- class GCDisabledGuard
- {
- private:
- int was_enabled = 0;
- public:
- GCDisabledGuard()
- : was_enabled(PyGC_IsEnabled())
- {
- PyGC_Disable();
- }
-
- ~GCDisabledGuard()
- {
- if (this->was_enabled) {
- PyGC_Enable();
- }
- }
- };
-#endif
-
- OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept;
-
- //TODO: Greenlet::g_switch() should call this automatically on its
- //return value. As it is, the module code is calling it.
- static inline OwnedObject
- single_result(const OwnedObject& results)
- {
- if (results
- && PyTuple_Check(results.borrow())
- && PyTuple_GET_SIZE(results.borrow()) == 1) {
- PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0);
- assert(result);
- return OwnedObject::owning(result);
- }
- return results;
- }
-
-
- static OwnedObject
- g_handle_exit(const OwnedObject& greenlet_result);
-
-
- template
- void operator<<(const PyThreadState *const lhs, T& rhs)
- {
- rhs.operator<<(lhs);
- }
-
-} // namespace greenlet ;
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp
deleted file mode 100644
index 0087d2f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of GreenletGlobals.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#ifndef T_GREENLET_GLOBALS
-#define T_GREENLET_GLOBALS
-
-#include "greenlet_refs.hpp"
-#include "greenlet_exceptions.hpp"
-#include "greenlet_thread_support.hpp"
-#include "greenlet_internal.hpp"
-
-namespace greenlet {
-
-// This encapsulates what were previously module global "constants"
-// established at init time.
-// This is a step towards Python3 style module state that allows
-// reloading.
-//
-// In an earlier iteration of this code, we used placement new to be
-// able to allocate this object statically still, so that references
-// to its members don't incur an extra pointer indirection.
-// But under some scenarios, that could result in crashes at
-// shutdown because apparently the destructor was getting run twice?
-class GreenletGlobals
-{
-
-public:
- const greenlet::refs::ImmortalEventName event_switch;
- const greenlet::refs::ImmortalEventName event_throw;
- const greenlet::refs::ImmortalException PyExc_GreenletError;
- const greenlet::refs::ImmortalException PyExc_GreenletExit;
- const greenlet::refs::ImmortalObject empty_tuple;
- const greenlet::refs::ImmortalObject empty_dict;
- const greenlet::refs::ImmortalString str_run;
- Mutex* const thread_states_to_destroy_lock;
- greenlet::cleanup_queue_t thread_states_to_destroy;
-
- GreenletGlobals() :
- event_switch("switch"),
- event_throw("throw"),
- PyExc_GreenletError("greenlet.error"),
- PyExc_GreenletExit("greenlet.GreenletExit", PyExc_BaseException),
- empty_tuple(Require(PyTuple_New(0))),
- empty_dict(Require(PyDict_New())),
- str_run("run"),
- thread_states_to_destroy_lock(new Mutex())
- {}
-
- ~GreenletGlobals()
- {
- // This object is (currently) effectively immortal, and not
- // just because of those placement new tricks; if we try to
- // deallocate the static object we allocated, and overwrote,
- // we would be doing so at C++ teardown time, which is after
- // the final Python GIL is released, and we can't use the API
- // then.
- // (The members will still be destructed, but they also don't
- // do any deallocation.)
- }
-
- void queue_to_destroy(ThreadState* ts) const
- {
- // we're currently accessed through a static const object,
- // implicitly marking our members as const, so code can't just
- // call push_back (or pop_back) without casting away the
- // const.
- //
- // Do that for callers.
- greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy);
- q.push_back(ts);
- }
-
- ThreadState* take_next_to_destroy() const
- {
- greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy);
- ThreadState* result = q.back();
- q.pop_back();
- return result;
- }
-};
-
-}; // namespace greenlet
-
-static const greenlet::GreenletGlobals* mod_globs;
-
-#endif // T_GREENLET_GLOBALS
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp
deleted file mode 100644
index ee01481..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of greenlet::MainGreenlet.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#ifndef T_MAIN_GREENLET_CPP
-#define T_MAIN_GREENLET_CPP
-
-#include "TGreenlet.hpp"
-
-#ifdef Py_GIL_DISABLED
-#include
-#endif
-
-// Incremented when we create a main greenlet, in a new thread, decremented
-// when it is destroyed.
-#ifdef Py_GIL_DISABLED
-static std::atomic G_TOTAL_MAIN_GREENLETS(0);
-#else
-// Protected by the GIL.
-static Py_ssize_t G_TOTAL_MAIN_GREENLETS;
-#endif
-
-namespace greenlet {
-greenlet::PythonAllocator MainGreenlet::allocator;
-
-void* MainGreenlet::operator new(size_t UNUSED(count))
-{
- return allocator.allocate(1);
-}
-
-
-void MainGreenlet::operator delete(void* ptr)
-{
- return allocator.deallocate(static_cast(ptr),
- 1);
-}
-
-
-MainGreenlet::MainGreenlet(PyGreenlet* p, ThreadState* state)
- : Greenlet(p, StackState::make_main()),
- _self(p),
- _thread_state(state)
-{
- G_TOTAL_MAIN_GREENLETS++;
-}
-
-MainGreenlet::~MainGreenlet()
-{
- G_TOTAL_MAIN_GREENLETS--;
- this->tp_clear();
-}
-
-ThreadState*
-MainGreenlet::thread_state() const noexcept
-{
- return this->_thread_state;
-}
-
-void
-MainGreenlet::thread_state(ThreadState* t) noexcept
-{
- assert(!t);
- this->_thread_state = t;
-}
-
-
-const BorrowedMainGreenlet
-MainGreenlet::main_greenlet() const
-{
- return this->_self;
-}
-
-BorrowedMainGreenlet
-MainGreenlet::find_main_greenlet_in_lineage() const
-{
- return BorrowedMainGreenlet(this->_self);
-}
-
-bool
-MainGreenlet::was_running_in_dead_thread() const noexcept
-{
- return !this->_thread_state;
-}
-
-OwnedObject
-MainGreenlet::g_switch()
-{
- try {
- this->check_switch_allowed();
- }
- catch (const PyErrOccurred&) {
- this->release_args();
- throw;
- }
-
- switchstack_result_t err = this->g_switchstack();
- if (err.status < 0) {
- // XXX: This code path is untested, but it is shared
- // with the UserGreenlet path that is tested.
- return this->on_switchstack_or_initialstub_failure(
- this,
- err,
- true, // target was me
- false // was initial stub
- );
- }
-
- return err.the_new_current_greenlet->g_switch_finish(err);
-}
-
-int
-MainGreenlet::tp_traverse(visitproc visit, void* arg)
-{
- if (this->_thread_state) {
- // we've already traversed main, (self), don't do it again.
- int result = this->_thread_state->tp_traverse(visit, arg, false);
- if (result) {
- return result;
- }
- }
- return Greenlet::tp_traverse(visit, arg);
-}
-
-const OwnedObject&
-MainGreenlet::run() const
-{
- throw AttributeError("Main greenlets do not have a run attribute.");
-}
-
-void
-MainGreenlet::run(const BorrowedObject UNUSED(nrun))
-{
- throw AttributeError("Main greenlets do not have a run attribute.");
-}
-
-void
-MainGreenlet::parent(const BorrowedObject raw_new_parent)
-{
- if (!raw_new_parent) {
- throw AttributeError("can't delete attribute");
- }
- throw AttributeError("cannot set the parent of a main greenlet");
-}
-
-const OwnedGreenlet
-MainGreenlet::parent() const
-{
- return OwnedGreenlet(); // null becomes None
-}
-
-}; // namespace greenlet
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp
deleted file mode 100644
index 6375922..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp
+++ /dev/null
@@ -1,439 +0,0 @@
-#ifndef GREENLET_PYTHON_STATE_CPP
-#define GREENLET_PYTHON_STATE_CPP
-
-#include
-#include "TGreenlet.hpp"
-
-namespace greenlet {
-
-PythonState::PythonState()
- : _top_frame()
-#if GREENLET_USE_CFRAME
- ,cframe(nullptr)
- ,use_tracing(0)
-#endif
-#if GREENLET_PY314
- ,py_recursion_depth(0)
- ,current_executor(nullptr)
- ,stackpointer(nullptr)
- #ifdef Py_GIL_DISABLED
- ,c_stack_refs(nullptr)
- #endif
-#elif GREENLET_PY312
- ,py_recursion_depth(0)
- ,c_recursion_depth(0)
-#else
- ,recursion_depth(0)
-#endif
-#if GREENLET_PY313
- ,delete_later(nullptr)
-#else
- ,trash_delete_nesting(0)
-#endif
-#if GREENLET_PY311
- ,current_frame(nullptr)
- ,datastack_chunk(nullptr)
- ,datastack_top(nullptr)
- ,datastack_limit(nullptr)
-#endif
-{
-#if GREENLET_USE_CFRAME
- /*
- The PyThreadState->cframe pointer usually points to memory on
- the stack, alloceted in a call into PyEval_EvalFrameDefault.
-
- Initially, before any evaluation begins, it points to the
- initial PyThreadState object's ``root_cframe`` object, which is
- statically allocated for the lifetime of the thread.
-
- A greenlet can last for longer than a call to
- PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer
- to be the current ``PyThreadState->cframe``; nor could we use
- one from the greenlet parent for the same reason. Yet a further
- no: we can't allocate one scoped to the greenlet and then
- destroy it when the greenlet is deallocated, because inside the
- interpreter the _PyCFrame objects form a linked list, and that too
- can result in accessing memory beyond its dynamic lifetime (if
- the greenlet doesn't actually finish before it dies, its entry
- could still be in the list).
-
- Using the ``root_cframe`` is problematic, though, because its
- members are never modified by the interpreter and are set to 0,
- meaning that its ``use_tracing`` flag is never updated. We don't
- want to modify that value in the ``root_cframe`` ourself: it
- *shouldn't* matter much because we should probably never get
- back to the point where that's the only cframe on the stack;
- even if it did matter, the major consequence of an incorrect
- value for ``use_tracing`` is that if its true the interpreter
- does some extra work --- however, it's just good code hygiene.
-
- Our solution: before a greenlet runs, after its initial
- creation, it uses the ``root_cframe`` just to have something to
- put there. However, once the greenlet is actually switched to
- for the first time, ``g_initialstub`` (which doesn't actually
- "return" while the greenlet is running) stores a new _PyCFrame on
- its local stack, and copies the appropriate values from the
- currently running _PyCFrame; this is then made the _PyCFrame for the
- newly-minted greenlet. ``g_initialstub`` then proceeds to call
- ``glet.run()``, which results in ``PyEval_...`` adding the
- _PyCFrame to the list. Switches continue as normal. Finally, when
- the greenlet finishes, the call to ``glet.run()`` returns and
- the _PyCFrame is taken out of the linked list and the stack value
- is now unused and free to expire.
-
- XXX: I think we can do better. If we're deallocing in the same
- thread, can't we traverse the list and unlink our frame?
- Can we just keep a reference to the thread state in case we
- dealloc in another thread? (Is that even possible if we're still
- running and haven't returned from g_initialstub?)
- */
- this->cframe = &PyThreadState_GET()->root_cframe;
-#endif
-}
-
-
-inline void PythonState::may_switch_away() noexcept
-{
-#if GREENLET_PY311
- // PyThreadState_GetFrame is probably going to have to allocate a
- // new frame object. That may trigger garbage collection. Because
- // we call this during the early phases of a switch (it doesn't
- // matter to which greenlet, as this has a global effect), if a GC
- // triggers a switch away, two things can happen, both bad:
- // - We might not get switched back to, halting forward progress.
- // this is pathological, but possible.
- // - We might get switched back to with a different set of
- // arguments or a throw instead of a switch. That would corrupt
- // our state (specifically, PyErr_Occurred() and this->args()
- // would no longer agree).
- //
- // Thus, when we call this API, we need to have GC disabled.
- // This method serves as a bottleneck we call when maybe beginning
- // a switch. In this way, it is always safe -- no risk of GC -- to
- // use ``_GetFrame()`` whenever we need to, just as it was in
- // <=3.10 (because subsequent calls will be cached and not
- // allocate memory).
-
- GCDisabledGuard no_gc;
- Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET()));
-#endif
-}
-
-void PythonState::operator<<(const PyThreadState *const tstate) noexcept
-{
- this->_context.steal(tstate->context);
-#if GREENLET_USE_CFRAME
- /*
- IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because
- the call to ``slp_switch()`` changes the contents of the stack,
- you cannot read from ``ts_current->cframe`` after that call and
- necessarily get the same values you get from reading it here.
- Anything you need to restore from now to then must be saved in a
- global/threadlocal variable (because we can't use stack
- variables here either). For things that need to persist across
- the switch, use `will_switch_from`.
- */
- this->cframe = tstate->cframe;
- #if !GREENLET_PY312
- this->use_tracing = tstate->cframe->use_tracing;
- #endif
-#endif // GREENLET_USE_CFRAME
-#if GREENLET_PY311
- #if GREENLET_PY314
- this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
- this->current_executor = tstate->current_executor;
- #ifdef Py_GIL_DISABLED
- this->c_stack_refs = ((_PyThreadStateImpl*)tstate)->c_stack_refs;
- #endif
- #elif GREENLET_PY312
- this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
- this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
- #else // not 312
- this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- #endif // GREENLET_PY312
- #if GREENLET_PY313
- this->current_frame = tstate->current_frame;
- #elif GREENLET_USE_CFRAME
- this->current_frame = tstate->cframe->current_frame;
- #endif
- this->datastack_chunk = tstate->datastack_chunk;
- this->datastack_top = tstate->datastack_top;
- this->datastack_limit = tstate->datastack_limit;
-
- PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate);
- Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
- // reference.
- this->_top_frame.steal(frame);
- #if GREENLET_PY314
- if (this->top_frame()) {
- this->stackpointer = this->_top_frame->f_frame->stackpointer;
- }
- else {
- this->stackpointer = nullptr;
- }
- #endif
- #if GREENLET_PY313
- this->delete_later = Py_XNewRef(tstate->delete_later);
- #elif GREENLET_PY312
- this->trash_delete_nesting = tstate->trash.delete_nesting;
- #else // not 312
- this->trash_delete_nesting = tstate->trash_delete_nesting;
- #endif // GREENLET_PY312
-#else // Not 311
- this->recursion_depth = tstate->recursion_depth;
- this->_top_frame.steal(tstate->frame);
- this->trash_delete_nesting = tstate->trash_delete_nesting;
-#endif // GREENLET_PY311
-}
-
-#if GREENLET_PY312
-void GREENLET_NOINLINE(PythonState::unexpose_frames)()
-{
- if (!this->top_frame()) {
- return;
- }
-
- // See GreenletState::expose_frames() and the comment on frames_were_exposed
- // for more information about this logic.
- _PyInterpreterFrame *iframe = this->_top_frame->f_frame;
- while (iframe != nullptr) {
- _PyInterpreterFrame *prev_exposed = iframe->previous;
- assert(iframe->frame_obj);
- memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0],
- sizeof(void *));
- iframe = prev_exposed;
- }
-}
-#else
-void PythonState::unexpose_frames()
-{}
-#endif
-
-void PythonState::operator>>(PyThreadState *const tstate) noexcept
-{
- tstate->context = this->_context.relinquish_ownership();
- /* Incrementing this value invalidates the contextvars cache,
- which would otherwise remain valid across switches */
- tstate->context_ver++;
-#if GREENLET_USE_CFRAME
- tstate->cframe = this->cframe;
- /*
- If we were tracing, we need to keep tracing.
- There should never be the possibility of hitting the
- root_cframe here. See note above about why we can't
- just copy this from ``origin->cframe->use_tracing``.
- */
- #if !GREENLET_PY312
- tstate->cframe->use_tracing = this->use_tracing;
- #endif
-#endif // GREENLET_USE_CFRAME
-#if GREENLET_PY311
- #if GREENLET_PY314
- tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
- tstate->current_executor = this->current_executor;
- #ifdef Py_GIL_DISABLED
- ((_PyThreadStateImpl*)tstate)->c_stack_refs = this->c_stack_refs;
- #endif
- this->unexpose_frames();
- #elif GREENLET_PY312
- tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
- tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth;
- this->unexpose_frames();
- #else // \/ 3.11
- tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
- #endif // GREENLET_PY312
- #if GREENLET_PY313
- tstate->current_frame = this->current_frame;
- #elif GREENLET_USE_CFRAME
- tstate->cframe->current_frame = this->current_frame;
- #endif
- tstate->datastack_chunk = this->datastack_chunk;
- tstate->datastack_top = this->datastack_top;
- tstate->datastack_limit = this->datastack_limit;
-#if GREENLET_PY314 && defined(Py_GIL_DISABLED)
- if (this->top_frame()) {
- this->_top_frame->f_frame->stackpointer = this->stackpointer;
- }
-#endif
- this->_top_frame.relinquish_ownership();
- #if GREENLET_PY313
- Py_XDECREF(tstate->delete_later);
- tstate->delete_later = this->delete_later;
- Py_CLEAR(this->delete_later);
- #elif GREENLET_PY312
- tstate->trash.delete_nesting = this->trash_delete_nesting;
- #else // not 3.12
- tstate->trash_delete_nesting = this->trash_delete_nesting;
- #endif // GREENLET_PY312
-#else // not 3.11
- tstate->frame = this->_top_frame.relinquish_ownership();
- tstate->recursion_depth = this->recursion_depth;
- tstate->trash_delete_nesting = this->trash_delete_nesting;
-#endif // GREENLET_PY311
-}
-
-inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept
-{
-#if GREENLET_USE_CFRAME && !GREENLET_PY312
- // The weird thing is, we don't actually save this for an
- // effect on the current greenlet, it's saved for an
- // effect on the target greenlet. That is, we want
- // continuity of this setting across the greenlet switch.
- this->use_tracing = origin_tstate->cframe->use_tracing;
-#endif
-}
-
-void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
-{
- this->_top_frame = nullptr;
-#if GREENLET_PY314
- this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
- this->current_executor = tstate->current_executor;
-#elif GREENLET_PY312
- this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
- // XXX: TODO: Comment from a reviewer:
- // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
- // But to me it looks more like that might not be the right
- // initialization either?
- this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
-#elif GREENLET_PY311
- this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
-#else
- this->recursion_depth = tstate->recursion_depth;
-#endif
-}
-// TODO: Better state management about when we own the top frame.
-int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept
-{
- Py_VISIT(this->_context.borrow());
- if (own_top_frame) {
- Py_VISIT(this->_top_frame.borrow());
- }
-#if GREENLET_PY314
- // TODO: Should we be visiting the c_stack_refs objects?
- // CPython uses a specific macro to do that which takes into
- // account boxing and null values and then calls
- // ``_PyGC_VisitStackRef``, but we don't have access to that, and
- // we can't duplicate it ourself (because it compares
- // ``visitproc`` to another function we can't access).
- // The naive way of looping over c_stack_refs->ref and visiting
- // those crashes the process (at least with GIL disabled).
-#endif
- return 0;
-}
-
-void PythonState::tp_clear(bool own_top_frame) noexcept
-{
- PythonStateContext::tp_clear();
- // If we get here owning a frame,
- // we got dealloc'd without being finished. We may or may not be
- // in the same thread.
- if (own_top_frame) {
- this->_top_frame.CLEAR();
- }
-}
-
-#if GREENLET_USE_CFRAME
-void PythonState::set_new_cframe(_PyCFrame& frame) noexcept
-{
- frame = *PyThreadState_GET()->cframe;
- /* Make the target greenlet refer to the stack value. */
- this->cframe = &frame;
- /*
- And restore the link to the previous frame so this one gets
- unliked appropriately.
- */
- this->cframe->previous = &PyThreadState_GET()->root_cframe;
-}
-#endif
-
-const PythonState::OwnedFrame& PythonState::top_frame() const noexcept
-{
- return this->_top_frame;
-}
-
-void PythonState::did_finish(PyThreadState* tstate) noexcept
-{
-#if GREENLET_PY311
- // See https://github.com/gevent/gevent/issues/1924 and
- // https://github.com/python-greenlet/greenlet/issues/328. In
- // short, Python 3.11 allocates memory for frames as a sort of
- // linked list that's kept as part of PyThreadState in the
- // ``datastack_chunk`` member and friends. These are saved and
- // restored as part of switching greenlets.
- //
- // When we initially switch to a greenlet, we set those to NULL.
- // That causes the frame management code to treat this like a
- // brand new thread and start a fresh list of chunks, beginning
- // with a new "root" chunk. As we make calls in this greenlet,
- // those chunks get added, and as calls return, they get popped.
- // But the frame code (pystate.c) is careful to make sure that the
- // root chunk never gets popped.
- //
- // Thus, when a greenlet exits for the last time, there will be at
- // least a single root chunk that we must be responsible for
- // deallocating.
- //
- // The complex part is that these chunks are allocated and freed
- // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public
- // functions, and they aren't exported for linking. It so happens
- // that we know they are just thin wrappers around the Arena
- // allocator, so we can use that directly to deallocate in a
- // compatible way.
- //
- // CAUTION: Check this implementation detail on every major version.
- //
- // It might be nice to be able to do this in our destructor, but
- // can we be sure that no one else is using that memory? Plus, as
- // described below, our pointers may not even be valid anymore. As
- // a special case, there is one time that we know we can do this,
- // and that's from the destructor of the associated UserGreenlet
- // (NOT main greenlet)
- PyObjectArenaAllocator alloc;
- _PyStackChunk* chunk = nullptr;
- if (tstate) {
- // We really did finish, we can never be switched to again.
- chunk = tstate->datastack_chunk;
- // Unfortunately, we can't do much sanity checking. Our
- // this->datastack_chunk pointer is out of date (evaluation may
- // have popped down through it already) so we can't verify that
- // we deallocate it. I don't think we can even check datastack_top
- // for the same reason.
-
- PyObject_GetArenaAllocator(&alloc);
- tstate->datastack_chunk = nullptr;
- tstate->datastack_limit = nullptr;
- tstate->datastack_top = nullptr;
-
- }
- else if (this->datastack_chunk) {
- // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're
- // still holding a stack chunk, it's garbage because we know
- // we can never switch back to let cPython clean it up.
- // Because the last time we got switched away from, and we
- // haven't run since then, we know our chain is valid and can
- // be dealloced.
- chunk = this->datastack_chunk;
- PyObject_GetArenaAllocator(&alloc);
- }
-
- if (alloc.free && chunk) {
- // In case the arena mechanism has been torn down already.
- while (chunk) {
- _PyStackChunk *prev = chunk->previous;
- chunk->previous = nullptr;
- alloc.free(alloc.ctx, chunk, chunk->size);
- chunk = prev;
- }
- }
-
- this->datastack_chunk = nullptr;
- this->datastack_limit = nullptr;
- this->datastack_top = nullptr;
-#endif
-}
-
-
-}; // namespace greenlet
-
-#endif // GREENLET_PYTHON_STATE_CPP
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TStackState.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TStackState.cpp
deleted file mode 100644
index 9743ab5..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TStackState.cpp
+++ /dev/null
@@ -1,265 +0,0 @@
-#ifndef GREENLET_STACK_STATE_CPP
-#define GREENLET_STACK_STATE_CPP
-
-#include "TGreenlet.hpp"
-
-namespace greenlet {
-
-#ifdef GREENLET_USE_STDIO
-#include
-using std::cerr;
-using std::endl;
-
-std::ostream& operator<<(std::ostream& os, const StackState& s)
-{
- os << "StackState(stack_start=" << (void*)s._stack_start
- << ", stack_stop=" << (void*)s.stack_stop
- << ", stack_copy=" << (void*)s.stack_copy
- << ", stack_saved=" << s._stack_saved
- << ", stack_prev=" << s.stack_prev
- << ", addr=" << &s
- << ")";
- return os;
-}
-#endif
-
-StackState::StackState(void* mark, StackState& current)
- : _stack_start(nullptr),
- stack_stop((char*)mark),
- stack_copy(nullptr),
- _stack_saved(0),
- /* Skip a dying greenlet */
- stack_prev(current._stack_start
- ? ¤t
- : current.stack_prev)
-{
-}
-
-StackState::StackState()
- : _stack_start(nullptr),
- stack_stop(nullptr),
- stack_copy(nullptr),
- _stack_saved(0),
- stack_prev(nullptr)
-{
-}
-
-StackState::StackState(const StackState& other)
-// can't use a delegating constructor because of
-// MSVC for Python 2.7
- : _stack_start(nullptr),
- stack_stop(nullptr),
- stack_copy(nullptr),
- _stack_saved(0),
- stack_prev(nullptr)
-{
- this->operator=(other);
-}
-
-StackState& StackState::operator=(const StackState& other)
-{
- if (&other == this) {
- return *this;
- }
- if (other._stack_saved) {
- throw std::runtime_error("Refusing to steal memory.");
- }
-
- //If we have memory allocated, dispose of it
- this->free_stack_copy();
-
- this->_stack_start = other._stack_start;
- this->stack_stop = other.stack_stop;
- this->stack_copy = other.stack_copy;
- this->_stack_saved = other._stack_saved;
- this->stack_prev = other.stack_prev;
- return *this;
-}
-
-inline void StackState::free_stack_copy() noexcept
-{
- PyMem_Free(this->stack_copy);
- this->stack_copy = nullptr;
- this->_stack_saved = 0;
-}
-
-inline void StackState::copy_heap_to_stack(const StackState& current) noexcept
-{
-
- /* Restore the heap copy back into the C stack */
- if (this->_stack_saved != 0) {
- memcpy(this->_stack_start, this->stack_copy, this->_stack_saved);
- this->free_stack_copy();
- }
- StackState* owner = const_cast(¤t);
- if (!owner->_stack_start) {
- owner = owner->stack_prev; /* greenlet is dying, skip it */
- }
- while (owner && owner->stack_stop <= this->stack_stop) {
- // cerr << "\tOwner: " << owner << endl;
- owner = owner->stack_prev; /* find greenlet with more stack */
- }
- this->stack_prev = owner;
- // cerr << "\tFinished with: " << *this << endl;
-}
-
-inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept
-{
- /* Save more of g's stack into the heap -- at least up to 'stop'
- g->stack_stop |________|
- | |
- | __ stop . . . . .
- | | ==> . .
- |________| _______
- | | | |
- | | | |
- g->stack_start | | |_______| g->stack_copy
- */
- intptr_t sz1 = this->_stack_saved;
- intptr_t sz2 = stop - this->_stack_start;
- assert(this->_stack_start);
- if (sz2 > sz1) {
- char* c = (char*)PyMem_Realloc(this->stack_copy, sz2);
- if (!c) {
- PyErr_NoMemory();
- return -1;
- }
- memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1);
- this->stack_copy = c;
- this->_stack_saved = sz2;
- }
- return 0;
-}
-
-inline int StackState::copy_stack_to_heap(char* const stackref,
- const StackState& current) noexcept
-{
- /* must free all the C stack up to target_stop */
- const char* const target_stop = this->stack_stop;
-
- StackState* owner = const_cast(¤t);
- assert(owner->_stack_saved == 0); // everything is present on the stack
- if (!owner->_stack_start) {
- owner = owner->stack_prev; /* not saved if dying */
- }
- else {
- owner->_stack_start = stackref;
- }
-
- while (owner->stack_stop < target_stop) {
- /* ts_current is entierely within the area to free */
- if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) {
- return -1; /* XXX */
- }
- owner = owner->stack_prev;
- }
- if (owner != this) {
- if (owner->copy_stack_to_heap_up_to(target_stop)) {
- return -1; /* XXX */
- }
- }
- return 0;
-}
-
-inline bool StackState::started() const noexcept
-{
- return this->stack_stop != nullptr;
-}
-
-inline bool StackState::main() const noexcept
-{
- return this->stack_stop == (char*)-1;
-}
-
-inline bool StackState::active() const noexcept
-{
- return this->_stack_start != nullptr;
-}
-
-inline void StackState::set_active() noexcept
-{
- assert(this->_stack_start == nullptr);
- this->_stack_start = (char*)1;
-}
-
-inline void StackState::set_inactive() noexcept
-{
- this->_stack_start = nullptr;
- // XXX: What if we still have memory out there?
- // That case is actually triggered by
- // test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks)
- // and
- // test_issue251_issue252_need_to_collect_in_background
- // (greenlet.tests.test_leaks.TestLeaks)
- //
- // Those objects never get deallocated, so the destructor never
- // runs.
- // It *seems* safe to clean up the memory here?
- if (this->_stack_saved) {
- this->free_stack_copy();
- }
-}
-
-inline intptr_t StackState::stack_saved() const noexcept
-{
- return this->_stack_saved;
-}
-
-inline char* StackState::stack_start() const noexcept
-{
- return this->_stack_start;
-}
-
-
-inline StackState StackState::make_main() noexcept
-{
- StackState s;
- s._stack_start = (char*)1;
- s.stack_stop = (char*)-1;
- return s;
-}
-
-StackState::~StackState()
-{
- if (this->_stack_saved != 0) {
- this->free_stack_copy();
- }
-}
-
-void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const
-{
- char* dest = static_cast(vdest);
- const char* src = static_cast(vsrc);
- if (src + n <= this->_stack_start
- || src >= this->_stack_start + this->_stack_saved
- || this->_stack_saved == 0) {
- // Nothing we're copying was spilled from the stack
- memcpy(dest, src, n);
- return;
- }
-
- if (src < this->_stack_start) {
- // Copy the part before the saved stack.
- // We know src + n > _stack_start due to the test above.
- const size_t nbefore = this->_stack_start - src;
- memcpy(dest, src, nbefore);
- dest += nbefore;
- src += nbefore;
- n -= nbefore;
- }
- // We know src >= _stack_start after the before-copy, and
- // src < _stack_start + _stack_saved due to the first if condition
- size_t nspilled = std::min(n, this->_stack_start + this->_stack_saved - src);
- memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled);
- dest += nspilled;
- src += nspilled;
- n -= nspilled;
- if (n > 0) {
- // Copy the part after the saved stack
- memcpy(dest, src, n);
- }
-}
-
-}; // namespace greenlet
-
-#endif // GREENLET_STACK_STATE_CPP
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp
deleted file mode 100644
index b3451a0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp
+++ /dev/null
@@ -1,523 +0,0 @@
-#ifndef GREENLET_THREAD_STATE_HPP
-#define GREENLET_THREAD_STATE_HPP
-
-#include
-#include
-#include
-
-#include "greenlet_internal.hpp"
-#include "greenlet_refs.hpp"
-#include "greenlet_thread_support.hpp"
-
-using greenlet::refs::BorrowedObject;
-using greenlet::refs::BorrowedGreenlet;
-using greenlet::refs::BorrowedMainGreenlet;
-using greenlet::refs::OwnedMainGreenlet;
-using greenlet::refs::OwnedObject;
-using greenlet::refs::OwnedGreenlet;
-using greenlet::refs::OwnedList;
-using greenlet::refs::PyErrFetchParam;
-using greenlet::refs::PyArgParseParam;
-using greenlet::refs::ImmortalString;
-using greenlet::refs::CreatedModule;
-using greenlet::refs::PyErrPieces;
-using greenlet::refs::NewReference;
-
-namespace greenlet {
-/**
- * Thread-local state of greenlets.
- *
- * Each native thread will get exactly one of these objects,
- * automatically accessed through the best available thread-local
- * mechanism the compiler supports (``thread_local`` for C++11
- * compilers or ``__thread``/``declspec(thread)`` for older GCC/clang
- * or MSVC, respectively.)
- *
- * Previously, we kept thread-local state mostly in a bunch of
- * ``static volatile`` variables in the main greenlet file.. This had
- * the problem of requiring extra checks, loops, and great care
- * accessing these variables if we potentially invoked any Python code
- * that could release the GIL, because the state could change out from
- * under us. Making the variables thread-local solves this problem.
- *
- * When we detected that a greenlet API accessing the current greenlet
- * was invoked from a different thread than the greenlet belonged to,
- * we stored a reference to the greenlet in the Python thread
- * dictionary for the thread the greenlet belonged to. This could lead
- * to memory leaks if the thread then exited (because of a reference
- * cycle, as greenlets referred to the thread dictionary, and deleting
- * non-current greenlets leaked their frame plus perhaps arguments on
- * the C stack). If a thread exited while still having running
- * greenlet objects (perhaps that had just switched back to the main
- * greenlet), and did not invoke one of the greenlet APIs *in that
- * thread, immediately before it exited, without some other thread
- * then being invoked*, such a leak was guaranteed.
- *
- * This can be partly solved by using compiler thread-local variables
- * instead of the Python thread dictionary, thus avoiding a cycle.
- *
- * To fully solve this problem, we need a reliable way to know that a
- * thread is done and we should clean up the main greenlet. On POSIX,
- * we can use the destructor function of ``pthread_key_create``, but
- * there's nothing similar on Windows; a C++11 thread local object
- * reliably invokes its destructor when the thread it belongs to exits
- * (non-C++11 compilers offer ``__thread`` or ``declspec(thread)`` to
- * create thread-local variables, but they can't hold C++ objects that
- * invoke destructors; the C++11 version is the most portable solution
- * I found). When the thread exits, we can drop references and
- * otherwise manipulate greenlets and frames that we know can no
- * longer be switched to.
- *
- * There are two small wrinkles. The first is that when the thread
- * exits, it is too late to actually invoke Python APIs: the Python
- * thread state is gone, and the GIL is released. To solve *this*
- * problem, our destructor uses ``Py_AddPendingCall`` to transfer the
- * destruction work to the main thread.
- *
- * The second is that once the thread exits, the thread local object
- * is invalid and we can't even access a pointer to it, so we can't
- * pass it to ``Py_AddPendingCall``. This is handled by actually using
- * a second object that's thread local (ThreadStateCreator) and having
- * it dynamically allocate this object so it can live until the
- * pending call runs.
- */
-
-
-
-class ThreadState {
-private:
- // As of commit 08ad1dd7012b101db953f492e0021fb08634afad
- // this class needed 56 bytes in o Py_DEBUG build
- // on 64-bit macOS 11.
- // Adding the vector takes us up to 80 bytes ()
-
- /* Strong reference to the main greenlet */
- OwnedMainGreenlet main_greenlet;
-
- /* Strong reference to the current greenlet. */
- OwnedGreenlet current_greenlet;
-
- /* Strong reference to the trace function, if any. */
- OwnedObject tracefunc;
-
- typedef std::vector > deleteme_t;
- /* A vector of raw PyGreenlet pointers representing things that need
- deleted when this thread is running. The vector owns the
- references, but you need to manually INCREF/DECREF as you use
- them. We don't use a vector because we
- make copy of this vector, and that would become O(n) as all the
- refcounts are incremented in the copy.
- */
- deleteme_t deleteme;
-
-#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
- void* exception_state;
-#endif
-
-#ifdef Py_GIL_DISABLED
- static std::atomic _clocks_used_doing_gc;
-#else
- static std::clock_t _clocks_used_doing_gc;
-#endif
- static ImmortalString get_referrers_name;
- static PythonAllocator allocator;
-
- G_NO_COPIES_OF_CLS(ThreadState);
-
-
- // Allocates a main greenlet for the thread state. If this fails,
- // exits the process. Called only during constructing a ThreadState.
- MainGreenlet* alloc_main()
- {
- PyGreenlet* gmain;
-
- /* create the main greenlet for this thread */
- gmain = reinterpret_cast(PyType_GenericAlloc(&PyGreenlet_Type, 0));
- if (gmain == NULL) {
- throw PyFatalError("alloc_main failed to alloc"); //exits the process
- }
-
- MainGreenlet* const main = new MainGreenlet(gmain, this);
-
- assert(Py_REFCNT(gmain) == 1);
- assert(gmain->pimpl == main);
- return main;
- }
-
-
-public:
- static void* operator new(size_t UNUSED(count))
- {
- return ThreadState::allocator.allocate(1);
- }
-
- static void operator delete(void* ptr)
- {
- return ThreadState::allocator.deallocate(static_cast(ptr),
- 1);
- }
-
- static void init()
- {
- ThreadState::get_referrers_name = "get_referrers";
- ThreadState::set_clocks_used_doing_gc(0);
- }
-
- ThreadState()
- {
-
-#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
- this->exception_state = slp_get_exception_state();
-#endif
-
- // XXX: Potentially dangerous, exposing a not fully
- // constructed object.
- MainGreenlet* const main = this->alloc_main();
- this->main_greenlet = OwnedMainGreenlet::consuming(
- main->self()
- );
- assert(this->main_greenlet);
- this->current_greenlet = main->self();
- // The main greenlet starts with 1 refs: The returned one. We
- // then copied it to the current greenlet.
- assert(this->main_greenlet.REFCNT() == 2);
- }
-
- inline void restore_exception_state()
- {
-#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
- // It's probably important this be inlined and only call C
- // functions to avoid adding an SEH frame.
- slp_set_exception_state(this->exception_state);
-#endif
- }
-
- inline bool has_main_greenlet() const noexcept
- {
- return bool(this->main_greenlet);
- }
-
- // Called from the ThreadStateCreator when we're in non-standard
- // threading mode. In that case, there is an object in the Python
- // thread state dictionary that points to us. The main greenlet
- // also traverses into us, in which case it's crucial not to
- // traverse back into the main greenlet.
- int tp_traverse(visitproc visit, void* arg, bool traverse_main=true)
- {
- if (traverse_main) {
- Py_VISIT(main_greenlet.borrow_o());
- }
- if (traverse_main || current_greenlet != main_greenlet) {
- Py_VISIT(current_greenlet.borrow_o());
- }
- Py_VISIT(tracefunc.borrow());
- return 0;
- }
-
- inline BorrowedMainGreenlet borrow_main_greenlet() const noexcept
- {
- assert(this->main_greenlet);
- assert(this->main_greenlet.REFCNT() >= 2);
- return this->main_greenlet;
- };
-
- inline OwnedMainGreenlet get_main_greenlet() const noexcept
- {
- return this->main_greenlet;
- }
-
- /**
- * In addition to returning a new reference to the currunt
- * greenlet, this performs any maintenance needed.
- */
- inline OwnedGreenlet get_current()
- {
- /* green_dealloc() cannot delete greenlets from other threads, so
- it stores them in the thread dict; delete them now. */
- this->clear_deleteme_list();
- //assert(this->current_greenlet->main_greenlet == this->main_greenlet);
- //assert(this->main_greenlet->main_greenlet == this->main_greenlet);
- return this->current_greenlet;
- }
-
- /**
- * As for non-const get_current();
- */
- inline BorrowedGreenlet borrow_current()
- {
- this->clear_deleteme_list();
- return this->current_greenlet;
- }
-
- /**
- * Does no maintenance.
- */
- inline OwnedGreenlet get_current() const
- {
- return this->current_greenlet;
- }
-
- template
- inline bool is_current(const refs::PyObjectPointer& obj) const
- {
- return this->current_greenlet.borrow_o() == obj.borrow_o();
- }
-
- inline void set_current(const OwnedGreenlet& target)
- {
- this->current_greenlet = target;
- }
-
-private:
- /**
- * Deref and remove the greenlets from the deleteme list. Must be
- * holding the GIL.
- *
- * If *murder* is true, then we must be called from a different
- * thread than the one that these greenlets were running in.
- * In that case, if the greenlet was actually running, we destroy
- * the frame reference and otherwise make it appear dead before
- * proceeding; otherwise, we would try (and fail) to raise an
- * exception in it and wind up right back in this list.
- */
- inline void clear_deleteme_list(const bool murder=false)
- {
- if (!this->deleteme.empty()) {
- // It's possible we could add items to this list while
- // running Python code if there's a thread switch, so we
- // need to defensively copy it before that can happen.
- deleteme_t copy = this->deleteme;
- this->deleteme.clear(); // in case things come back on the list
- for(deleteme_t::iterator it = copy.begin(), end = copy.end();
- it != end;
- ++it ) {
- PyGreenlet* to_del = *it;
- if (murder) {
- // Force each greenlet to appear dead; we can't raise an
- // exception into it anymore anyway.
- to_del->pimpl->murder_in_place();
- }
-
- // The only reference to these greenlets should be in
- // this list, decreffing them should let them be
- // deleted again, triggering calls to green_dealloc()
- // in the correct thread (if we're not murdering).
- // This may run arbitrary Python code and switch
- // threads or greenlets!
- Py_DECREF(to_del);
- if (PyErr_Occurred()) {
- PyErr_WriteUnraisable(nullptr);
- PyErr_Clear();
- }
- }
- }
- }
-
-public:
-
- /**
- * Returns a new reference, or a false object.
- */
- inline OwnedObject get_tracefunc() const
- {
- return tracefunc;
- };
-
-
- inline void set_tracefunc(BorrowedObject tracefunc)
- {
- assert(tracefunc);
- if (tracefunc == BorrowedObject(Py_None)) {
- this->tracefunc.CLEAR();
- }
- else {
- this->tracefunc = tracefunc;
- }
- }
-
- /**
- * Given a reference to a greenlet that some other thread
- * attempted to delete (has a refcount of 0) store it for later
- * deletion when the thread this state belongs to is current.
- */
- inline void delete_when_thread_running(PyGreenlet* to_del)
- {
- Py_INCREF(to_del);
- this->deleteme.push_back(to_del);
- }
-
- /**
- * Set to std::clock_t(-1) to disable.
- */
- inline static std::clock_t clocks_used_doing_gc()
- {
-#ifdef Py_GIL_DISABLED
- return ThreadState::_clocks_used_doing_gc.load(std::memory_order_relaxed);
-#else
- return ThreadState::_clocks_used_doing_gc;
-#endif
- }
-
- inline static void set_clocks_used_doing_gc(std::clock_t value)
- {
-#ifdef Py_GIL_DISABLED
- ThreadState::_clocks_used_doing_gc.store(value, std::memory_order_relaxed);
-#else
- ThreadState::_clocks_used_doing_gc = value;
-#endif
- }
-
- inline static void add_clocks_used_doing_gc(std::clock_t value)
- {
-#ifdef Py_GIL_DISABLED
- ThreadState::_clocks_used_doing_gc.fetch_add(value, std::memory_order_relaxed);
-#else
- ThreadState::_clocks_used_doing_gc += value;
-#endif
- }
-
- ~ThreadState()
- {
- if (!PyInterpreterState_Head()) {
- // We shouldn't get here (our callers protect us)
- // but if we do, all we can do is bail early.
- return;
- }
-
- // We should not have an "origin" greenlet; that only exists
- // for the temporary time during a switch, which should not
- // be in progress as the thread dies.
- //assert(!this->switching_state.origin);
-
- this->tracefunc.CLEAR();
-
- // Forcibly GC as much as we can.
- this->clear_deleteme_list(true);
-
- // The pending call did this.
- assert(this->main_greenlet->thread_state() == nullptr);
-
- // If the main greenlet is the current greenlet,
- // then we "fell off the end" and the thread died.
- // It's possible that there is some other greenlet that
- // switched to us, leaving a reference to the main greenlet
- // on the stack, somewhere uncollectible. Try to detect that.
- if (this->current_greenlet == this->main_greenlet && this->current_greenlet) {
- assert(this->current_greenlet->is_currently_running_in_some_thread());
- // Drop one reference we hold.
- this->current_greenlet.CLEAR();
- assert(!this->current_greenlet);
- // Only our reference to the main greenlet should be left,
- // But hold onto the pointer in case we need to do extra cleanup.
- PyGreenlet* old_main_greenlet = this->main_greenlet.borrow();
- Py_ssize_t cnt = this->main_greenlet.REFCNT();
- this->main_greenlet.CLEAR();
- if (ThreadState::clocks_used_doing_gc() != std::clock_t(-1)
- && cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) {
- // Highly likely that the reference is somewhere on
- // the stack, not reachable by GC. Verify.
- // XXX: This is O(n) in the total number of objects.
- // TODO: Add a way to disable this at runtime, and
- // another way to report on it.
- std::clock_t begin = std::clock();
- NewReference gc(PyImport_ImportModule("gc"));
- if (gc) {
- OwnedObject get_referrers = gc.PyRequireAttr(ThreadState::get_referrers_name);
- OwnedList refs(get_referrers.PyCall(old_main_greenlet));
- if (refs && refs.empty()) {
- assert(refs.REFCNT() == 1);
- // We found nothing! So we left a dangling
- // reference: Probably the last thing some
- // other greenlet did was call
- // 'getcurrent().parent.switch()' to switch
- // back to us. Clean it up. This will be the
- // case on CPython 3.7 and newer, as they use
- // an internal calling conversion that avoids
- // creating method objects and storing them on
- // the stack.
- Py_DECREF(old_main_greenlet);
- }
- else if (refs
- && refs.size() == 1
- && PyCFunction_Check(refs.at(0))
- && Py_REFCNT(refs.at(0)) == 2) {
- assert(refs.REFCNT() == 1);
- // Ok, we found a C method that refers to the
- // main greenlet, and its only referenced
- // twice, once in the list we just created,
- // once from...somewhere else. If we can't
- // find where else, then this is a leak.
- // This happens in older versions of CPython
- // that create a bound method object somewhere
- // on the stack that we'll never get back to.
- if (PyCFunction_GetFunction(refs.at(0).borrow()) == (PyCFunction)green_switch) {
- BorrowedObject function_w = refs.at(0);
- refs.clear(); // destroy the reference
- // from the list.
- // back to one reference. Can *it* be
- // found?
- assert(function_w.REFCNT() == 1);
- refs = get_referrers.PyCall(function_w);
- if (refs && refs.empty()) {
- // Nope, it can't be found so it won't
- // ever be GC'd. Drop it.
- Py_CLEAR(function_w);
- }
- }
- }
- std::clock_t end = std::clock();
- ThreadState::add_clocks_used_doing_gc(end - begin);
- }
- }
- }
-
- // We need to make sure this greenlet appears to be dead,
- // because otherwise deallocing it would fail to raise an
- // exception in it (the thread is dead) and put it back in our
- // deleteme list.
- if (this->current_greenlet) {
- this->current_greenlet->murder_in_place();
- this->current_greenlet.CLEAR();
- }
-
- if (this->main_greenlet) {
- // Couldn't have been the main greenlet that was running
- // when the thread exited (because we already cleared this
- // pointer if it was). This shouldn't be possible?
-
- // If the main greenlet was current when the thread died (it
- // should be, right?) then we cleared its self pointer above
- // when we cleared the current greenlet's main greenlet pointer.
- // assert(this->main_greenlet->main_greenlet == this->main_greenlet
- // || !this->main_greenlet->main_greenlet);
- // // self reference, probably gone
- // this->main_greenlet->main_greenlet.CLEAR();
-
- // This will actually go away when the ivar is destructed.
- this->main_greenlet.CLEAR();
- }
-
- if (PyErr_Occurred()) {
- PyErr_WriteUnraisable(NULL);
- PyErr_Clear();
- }
-
- }
-
-};
-
-ImmortalString ThreadState::get_referrers_name(nullptr);
-PythonAllocator ThreadState::allocator;
-#ifdef Py_GIL_DISABLED
-std::atomic ThreadState::_clocks_used_doing_gc(0);
-#else
-std::clock_t ThreadState::_clocks_used_doing_gc(0);
-#endif
-
-
-
-
-
-}; // namespace greenlet
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp
deleted file mode 100644
index ebd33a3..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp
+++ /dev/null
@@ -1,102 +0,0 @@
-#ifndef GREENLET_THREAD_STATE_CREATOR_HPP
-#define GREENLET_THREAD_STATE_CREATOR_HPP
-
-#include
-#include
-
-#include "greenlet_internal.hpp"
-#include "greenlet_refs.hpp"
-#include "greenlet_thread_support.hpp"
-
-#include "TThreadState.hpp"
-
-namespace greenlet {
-
-
-typedef void (*ThreadStateDestructor)(ThreadState* const);
-
-// Only one of these, auto created per thread as a thread_local.
-// Constructing the state constructs the MainGreenlet.
-template
-class ThreadStateCreator
-{
-private:
- // Initialized to 1, and, if still 1, created on access.
- // Set to 0 on destruction.
- ThreadState* _state;
- G_NO_COPIES_OF_CLS(ThreadStateCreator);
-
- inline bool has_initialized_state() const noexcept
- {
- return this->_state != (ThreadState*)1;
- }
-
- inline bool has_state() const noexcept
- {
- return this->has_initialized_state() && this->_state != nullptr;
- }
-
-public:
-
- ThreadStateCreator() :
- _state((ThreadState*)1)
- {
- }
-
- ~ThreadStateCreator()
- {
- if (this->has_state()) {
- Destructor(this->_state);
- }
-
- this->_state = nullptr;
- }
-
- inline ThreadState& state()
- {
- // The main greenlet will own this pointer when it is created,
- // which will be right after this. The plan is to give every
- // greenlet a pointer to the main greenlet for the thread it
- // runs in; if we are doing something cross-thread, we need to
- // access the pointer from the main greenlet. Deleting the
- // thread, and hence the thread-local storage, will delete the
- // state pointer in the main greenlet.
- if (!this->has_initialized_state()) {
- // XXX: Assuming allocation never fails
- this->_state = new ThreadState;
- // For non-standard threading, we need to store an object
- // in the Python thread state dictionary so that it can be
- // DECREF'd when the thread ends (ideally; the dict could
- // last longer) and clean this object up.
- }
- if (!this->_state) {
- throw std::runtime_error("Accessing state after destruction.");
- }
- return *this->_state;
- }
-
- operator ThreadState&()
- {
- return this->state();
- }
-
- operator ThreadState*()
- {
- return &this->state();
- }
-
- inline int tp_traverse(visitproc visit, void* arg)
- {
- if (this->has_state()) {
- return this->_state->tp_traverse(visit, arg);
- }
- return 0;
- }
-
-};
-
-
-
-}; // namespace greenlet
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp
deleted file mode 100644
index ae0b9ae..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp
+++ /dev/null
@@ -1,223 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of the ThreadState destructors.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#ifndef T_THREADSTATE_DESTROY
-#define T_THREADSTATE_DESTROY
-
-#include "TGreenlet.hpp"
-
-#include "greenlet_thread_support.hpp"
-#include "greenlet_compiler_compat.hpp"
-#include "TGreenletGlobals.cpp"
-#include "TThreadState.hpp"
-#include "TThreadStateCreator.hpp"
-
-namespace greenlet {
-
-extern "C" {
-
-struct ThreadState_DestroyNoGIL
-{
- /**
- This function uses the same lock that the PendingCallback does
- */
- static void
- MarkGreenletDeadAndQueueCleanup(ThreadState* const state)
- {
-#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK
- // One rare platform.
- return;
-#endif
- // We are *NOT* holding the GIL. Our thread is in the middle
- // of its death throes and the Python thread state is already
- // gone so we can't use most Python APIs. One that is safe is
- // ``Py_AddPendingCall``, unless the interpreter itself has
- // been torn down. There is a limited number of calls that can
- // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we
- // coalesce these calls using our own queue.
-
- if (!MarkGreenletDeadIfNeeded(state)) {
- // No state, or no greenlet
- return;
- }
-
- // XXX: Because we don't have the GIL, this is a race condition.
- if (!PyInterpreterState_Head()) {
- // We have to leak the thread state, if the
- // interpreter has shut down when we're getting
- // deallocated, we can't run the cleanup code that
- // deleting it would imply.
- return;
- }
-
- AddToCleanupQueue(state);
-
- }
-
-private:
-
- // If the state has an allocated main greenlet:
- // - mark the greenlet as dead by disassociating it from the state;
- // - return 1
- // Otherwise, return 0.
- static bool
- MarkGreenletDeadIfNeeded(ThreadState* const state)
- {
- if (!state) {
- return false;
- }
- LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
- if (state->has_main_greenlet()) {
- // mark the thread as dead ASAP.
- // this is racy! If we try to throw or switch to a
- // greenlet from this thread from some other thread before
- // we clear the state pointer, it won't realize the state
- // is dead which can crash the process.
- PyGreenlet* p(state->borrow_main_greenlet().borrow());
- assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr);
- dynamic_cast(p->pimpl)->thread_state(nullptr);
- return true;
- }
- return false;
- }
-
- static void
- AddToCleanupQueue(ThreadState* const state)
- {
- assert(state && state->has_main_greenlet());
-
- // NOTE: Because we're not holding the GIL here, some other
- // Python thread could run and call ``os.fork()``, which would
- // be bad if that happened while we are holding the cleanup
- // lock (it wouldn't function in the child process).
- // Make a best effort to try to keep the duration we hold the
- // lock short.
- // TODO: On platforms that support it, use ``pthread_atfork`` to
- // drop this lock.
- LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
-
- mod_globs->queue_to_destroy(state);
- if (mod_globs->thread_states_to_destroy.size() == 1) {
- // We added the first item to the queue. We need to schedule
- // the cleanup.
-
- // A size greater than 1 means that we have already added the pending call,
- // and in fact, it may be executing now.
- // If it is executing, our lock makes sure that it will see the item we just added
- // to the queue on its next iteration (after we release the lock)
- //
- // A size of 1 means there is no pending call, OR the pending call is
- // currently executing, has dropped the lock, and is deleting the last item
- // from the queue; its next iteration will go ahead and delete the item we just added.
- // And the pending call we schedule here will have no work to do.
- int result = AddPendingCall(
- PendingCallback_DestroyQueue,
- nullptr);
- if (result < 0) {
- // Hmm, what can we do here?
- fprintf(stderr,
- "greenlet: WARNING: failed in call to Py_AddPendingCall; "
- "expect a memory leak.\n");
- }
- }
- }
-
- static int
- PendingCallback_DestroyQueue(void* UNUSED(arg))
- {
- // We're may or may not be holding the GIL here (depending on
- // Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
- // be possible.
- while (1) {
- ThreadState* to_destroy;
- {
- LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
- if (mod_globs->thread_states_to_destroy.empty()) {
- break;
- }
- to_destroy = mod_globs->take_next_to_destroy();
- }
- assert(to_destroy);
- assert(to_destroy->has_main_greenlet());
- // Drop the lock while we do the actual deletion.
- // This allows other calls to MarkGreenletDeadAndQueueCleanup
- // to enter and add to our queue.
- DestroyOne(to_destroy);
- }
- return 0;
- }
-
- static void
- DestroyOne(const ThreadState* const state)
- {
- // May or may not be holding the GIL (depending on Py_GIL_DISABLED).
- // Passed a non-shared pointer to the actual thread state.
- // state -> main greenlet
- assert(state->has_main_greenlet());
- PyGreenlet* main(state->borrow_main_greenlet());
- // When we need to do cross-thread operations, we check this.
- // A NULL value means the thread died some time ago.
- // We do this here, rather than in a Python dealloc function
- // for the greenlet, in case there's still a reference out
- // there.
- dynamic_cast(main->pimpl)->thread_state(nullptr);
-
- delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
- }
-
-
- static int AddPendingCall(int (*func)(void*), void* arg)
- {
- // If the interpreter is in the middle of finalizing, we can't add a
- // pending call. Trying to do so will end up in a SIGSEGV, as
- // Py_AddPendingCall will not be able to get the interpreter and will
- // try to dereference a NULL pointer. It's possible this can still
- // segfault if we happen to get context switched, and maybe we should
- // just always implement our own AddPendingCall, but I'd like to see if
- // this works first
-#if GREENLET_PY313
- if (Py_IsFinalizing()) {
-#else
- if (_Py_IsFinalizing()) {
-#endif
-#ifdef GREENLET_DEBUG
- // No need to log in the general case. Yes, we'll leak,
- // but we're shutting down so it should be ok.
- fprintf(stderr,
- "greenlet: WARNING: Interpreter is finalizing. Ignoring "
- "call to Py_AddPendingCall; \n");
-#endif
- return 0;
- }
- return Py_AddPendingCall(func, arg);
- }
-
-
-
-
-
-};
-};
-
-}; // namespace greenlet
-
-// The intent when GET_THREAD_STATE() is needed multiple times in a
-// function is to take a reference to its return value in a local
-// variable, to avoid the thread-local indirection. On some platforms
-// (macOS), accessing a thread-local involves a function call (plus an
-// initial function call in each function that uses a thread local);
-// in contrast, static volatile variables are at some pre-computed
-// offset.
-typedef greenlet::ThreadStateCreator ThreadStateCreator;
-static thread_local ThreadStateCreator g_thread_state_global;
-#define GET_THREAD_STATE() g_thread_state_global
-
-#endif //T_THREADSTATE_DESTROY
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp
deleted file mode 100644
index 73a8133..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp
+++ /dev/null
@@ -1,662 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/**
- * Implementation of greenlet::UserGreenlet.
- *
- * Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#ifndef T_USER_GREENLET_CPP
-#define T_USER_GREENLET_CPP
-
-#include "greenlet_internal.hpp"
-#include "TGreenlet.hpp"
-
-#include "TThreadStateDestroy.cpp"
-
-
-namespace greenlet {
-using greenlet::refs::BorrowedMainGreenlet;
-greenlet::PythonAllocator UserGreenlet::allocator;
-
-void* UserGreenlet::operator new(size_t UNUSED(count))
-{
- return allocator.allocate(1);
-}
-
-
-void UserGreenlet::operator delete(void* ptr)
-{
- return allocator.deallocate(static_cast(ptr),
- 1);
-}
-
-
-UserGreenlet::UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent)
- : Greenlet(p), _parent(the_parent)
-{
-}
-
-UserGreenlet::~UserGreenlet()
-{
- // Python 3.11: If we don't clear out the raw frame datastack
- // when deleting an unfinished greenlet,
- // TestLeaks.test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main fails.
- this->python_state.did_finish(nullptr);
- this->tp_clear();
-}
-
-
-const BorrowedMainGreenlet
-UserGreenlet::main_greenlet() const
-{
- return this->_main_greenlet;
-}
-
-
-BorrowedMainGreenlet
-UserGreenlet::find_main_greenlet_in_lineage() const
-{
- if (this->started()) {
- assert(this->_main_greenlet);
- return BorrowedMainGreenlet(this->_main_greenlet);
- }
-
- if (!this->_parent) {
- /* garbage collected greenlet in chain */
- // XXX: WHAT?
- return BorrowedMainGreenlet(nullptr);
- }
-
- return this->_parent->find_main_greenlet_in_lineage();
-}
-
-
-/**
- * CAUTION: This will allocate memory and may trigger garbage
- * collection and arbitrary Python code.
- */
-OwnedObject
-UserGreenlet::throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state)
-{
- /* The dying greenlet cannot be a parent of ts_current
- because the 'parent' field chain would hold a
- reference */
- UserGreenlet::ParentIsCurrentGuard with_current_parent(this, current_thread_state);
-
- // We don't care about the return value, only whether an
- // exception happened. Whether or not an exception happens,
- // we need to restore the parent in case the greenlet gets
- // resurrected.
- return Greenlet::throw_GreenletExit_during_dealloc(current_thread_state);
-}
-
-ThreadState*
-UserGreenlet::thread_state() const noexcept
-{
- // TODO: maybe make this throw, if the thread state isn't there?
- // if (!this->main_greenlet) {
- // throw std::runtime_error("No thread state"); // TODO: Better exception
- // }
- if (!this->_main_greenlet) {
- return nullptr;
- }
- return this->_main_greenlet->thread_state();
-}
-
-
-bool
-UserGreenlet::was_running_in_dead_thread() const noexcept
-{
- return this->_main_greenlet && !this->thread_state();
-}
-
-OwnedObject
-UserGreenlet::g_switch()
-{
- assert(this->args() || PyErr_Occurred());
-
- try {
- this->check_switch_allowed();
- }
- catch (const PyErrOccurred&) {
- this->release_args();
- throw;
- }
-
- // Switching greenlets used to attempt to clean out ones that need
- // deleted *if* we detected a thread switch. Should it still do
- // that?
- // An issue is that if we delete a greenlet from another thread,
- // it gets queued to this thread, and ``kill_greenlet()`` switches
- // back into the greenlet
-
- /* find the real target by ignoring dead greenlets,
- and if necessary starting a greenlet. */
- switchstack_result_t err;
- Greenlet* target = this;
- // TODO: probably cleaner to handle the case where we do
- // switch to ourself separately from the other cases.
- // This can probably even further be simplified if we keep
- // track of the switching_state we're going for and just call
- // into g_switch() if it's not ourself. The main problem with that
- // is that we would be using more stack space.
- bool target_was_me = true;
- bool was_initial_stub = false;
- while (target) {
- if (target->active()) {
- if (!target_was_me) {
- target->args() <<= this->args();
- assert(!this->args());
- }
- err = target->g_switchstack();
- break;
- }
- if (!target->started()) {
- // We never encounter a main greenlet that's not started.
- assert(!target->main());
- UserGreenlet* real_target = static_cast(target);
- assert(real_target);
- void* dummymarker;
- was_initial_stub = true;
- if (!target_was_me) {
- target->args() <<= this->args();
- assert(!this->args());
- }
- try {
- // This can only throw back to us while we're
- // still in this greenlet. Once the new greenlet
- // is bootstrapped, it has its own exception state.
- err = real_target->g_initialstub(&dummymarker);
- }
- catch (const PyErrOccurred&) {
- this->release_args();
- throw;
- }
- catch (const GreenletStartedWhileInPython&) {
- // The greenlet was started sometime before this
- // greenlet actually switched to it, i.e.,
- // "concurrent" calls to switch() or throw().
- // We need to retry the switch.
- // Note that the current greenlet has been reset
- // to this one (or we wouldn't be running!)
- continue;
- }
- break;
- }
-
- target = target->parent();
- target_was_me = false;
- }
- // The ``this`` pointer and all other stack or register based
- // variables are invalid now, at least where things succeed
- // above.
- // But this one, probably not so much? It's not clear if it's
- // safe to throw an exception at this point.
-
- if (err.status < 0) {
- // If we get here, either g_initialstub()
- // failed, or g_switchstack() failed. Either one of those
- // cases SHOULD leave us in the original greenlet with a valid
- // stack.
- return this->on_switchstack_or_initialstub_failure(target, err, target_was_me, was_initial_stub);
- }
-
- // err.the_new_current_greenlet would be the same as ``target``,
- // if target wasn't probably corrupt.
- return err.the_new_current_greenlet->g_switch_finish(err);
-}
-
-
-
-Greenlet::switchstack_result_t
-UserGreenlet::g_initialstub(void* mark)
-{
- OwnedObject run;
-
- // We need to grab a reference to the current switch arguments
- // in case we're entered concurrently during the call to
- // GetAttr() and have to try again.
- // We'll restore them when we return in that case.
- // Scope them tightly to avoid ref leaks.
- {
- SwitchingArgs args(this->args());
-
- /* save exception in case getattr clears it */
- PyErrPieces saved;
-
- /*
- self.run is the object to call in the new greenlet.
- This could run arbitrary python code and switch greenlets!
- */
- run = this->self().PyRequireAttr(mod_globs->str_run);
- /* restore saved exception */
- saved.PyErrRestore();
-
-
- /* recheck that it's safe to switch in case greenlet reparented anywhere above */
- this->check_switch_allowed();
-
- /* by the time we got here another start could happen elsewhere,
- * that means it should now be a regular switch.
- * This can happen if the Python code is a subclass that implements
- * __getattribute__ or __getattr__, or makes ``run`` a descriptor;
- * all of those can run arbitrary code that switches back into
- * this greenlet.
- */
- if (this->stack_state.started()) {
- // the successful switch cleared these out, we need to
- // restore our version. They will be copied on up to the
- // next target.
- assert(!this->args());
- this->args() <<= args;
- throw GreenletStartedWhileInPython();
- }
- }
-
- // Sweet, if we got here, we have the go-ahead and will switch
- // greenlets.
- // Nothing we do from here on out should allow for a thread or
- // greenlet switch: No arbitrary calls to Python, including
- // decref'ing
-
-#if GREENLET_USE_CFRAME
- /* OK, we need it, we're about to switch greenlets, save the state. */
- /*
- See green_new(). This is a stack-allocated variable used
- while *self* is in PyObject_Call().
- We want to defer copying the state info until we're sure
- we need it and are in a stable place to do so.
- */
- _PyCFrame trace_info;
-
- this->python_state.set_new_cframe(trace_info);
-#endif
- /* start the greenlet */
- ThreadState& thread_state = GET_THREAD_STATE().state();
- this->stack_state = StackState(mark,
- thread_state.borrow_current()->stack_state);
- this->python_state.set_initial_state(PyThreadState_GET());
- this->exception_state.clear();
- this->_main_greenlet = thread_state.get_main_greenlet();
-
- /* perform the initial switch */
- switchstack_result_t err = this->g_switchstack();
- /* returns twice!
- The 1st time with ``err == 1``: we are in the new greenlet.
- This one owns a greenlet that used to be current.
- The 2nd time with ``err <= 0``: back in the caller's
- greenlet; this happens if the child finishes or switches
- explicitly to us. Either way, the ``err`` variable is
- created twice at the same memory location, but possibly
- having different ``origin`` values. Note that it's not
- constructed for the second time until the switch actually happens.
- */
- if (err.status == 1) {
- // In the new greenlet.
-
- // This never returns! Calling inner_bootstrap steals
- // the contents of our run object within this stack frame, so
- // it is not valid to do anything with it.
- try {
- this->inner_bootstrap(err.origin_greenlet.relinquish_ownership(),
- run.relinquish_ownership());
- }
- // Getting a C++ exception here isn't good. It's probably a
- // bug in the underlying greenlet, meaning it's probably a
- // C++ extension. We're going to abort anyway, but try to
- // display some nice information *if* possible. Some obscure
- // platforms don't properly support this (old 32-bit Arm, see see
- // https://github.com/python-greenlet/greenlet/issues/385); that's not
- // great, but should usually be OK because, as mentioned above, we're
- // terminating anyway.
- //
- // The catching is tested by
- // ``test_cpp.CPPTests.test_unhandled_exception_in_greenlet_aborts``.
- //
- // PyErrOccurred can theoretically be thrown by
- // inner_bootstrap() -> g_switch_finish(), but that should
- // never make it back to here. It is a std::exception and
- // would be caught if it is.
- catch (const std::exception& e) {
- std::string base = "greenlet: Unhandled C++ exception: ";
- base += e.what();
- Py_FatalError(base.c_str());
- }
- catch (...) {
- // Some compilers/runtimes use exceptions internally.
- // It appears that GCC on Linux with libstdc++ throws an
- // exception internally at process shutdown time to unwind
- // stacks and clean up resources. Depending on exactly
- // where we are when the process exits, that could result
- // in an unknown exception getting here. If we
- // Py_FatalError() or abort() here, we interfere with
- // orderly process shutdown. Throwing the exception on up
- // is the right thing to do.
- //
- // gevent's ``examples/dns_mass_resolve.py`` demonstrates this.
-#ifndef NDEBUG
- fprintf(stderr,
- "greenlet: inner_bootstrap threw unknown exception; "
- "is the process terminating?\n");
-#endif
- throw;
- }
- Py_FatalError("greenlet: inner_bootstrap returned with no exception.\n");
- }
-
-
- // In contrast, notice that we're keeping the origin greenlet
- // around as an owned reference; we need it to call the trace
- // function for the switch back into the parent. It was only
- // captured at the time the switch actually happened, though,
- // so we haven't been keeping an extra reference around this
- // whole time.
-
- /* back in the parent */
- if (err.status < 0) {
- /* start failed badly, restore greenlet state */
- this->stack_state = StackState();
- this->_main_greenlet.CLEAR();
- // CAUTION: This may run arbitrary Python code.
- run.CLEAR(); // inner_bootstrap didn't run, we own the reference.
- }
-
- // In the success case, the spawned code (inner_bootstrap) will
- // take care of decrefing this, so we relinquish ownership so as
- // to not double-decref.
-
- run.relinquish_ownership();
-
- return err;
-}
-
-
-void
-UserGreenlet::inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run)
-{
- // The arguments here would be another great place for move.
- // As it is, we take them as a reference so that when we clear
- // them we clear what's on the stack above us. Do that NOW, and
- // without using a C++ RAII object,
- // so there's no way that exiting the parent frame can clear it,
- // or we clear it unexpectedly. This arises in the context of the
- // interpreter shutting down. See https://github.com/python-greenlet/greenlet/issues/325
- //PyObject* run = _run.relinquish_ownership();
-
- /* in the new greenlet */
- assert(this->thread_state()->borrow_current() == BorrowedGreenlet(this->_self));
- // C++ exceptions cannot propagate to the parent greenlet from
- // here. (TODO: Do we need a catch(...) clause, perhaps on the
- // function itself? ALl we could do is terminate the program.)
- // NOTE: On 32-bit Windows, the call chain is extremely
- // important here in ways that are subtle, having to do with
- // the depth of the SEH list. The call to restore it MUST NOT
- // add a new SEH handler to the list, or we'll restore it to
- // the wrong thing.
- this->thread_state()->restore_exception_state();
- /* stack variables from above are no good and also will not unwind! */
- // EXCEPT: That can't be true, we access run, among others, here.
-
- this->stack_state.set_active(); /* running */
-
- // We're about to possibly run Python code again, which
- // could switch back/away to/from us, so we need to grab the
- // arguments locally.
- SwitchingArgs args;
- args <<= this->args();
- assert(!this->args());
-
- // XXX: We could clear this much earlier, right?
- // Or would that introduce the possibility of running Python
- // code when we don't want to?
- // CAUTION: This may run arbitrary Python code.
- this->_run_callable.CLEAR();
-
-
- // The first switch we need to manually call the trace
- // function here instead of in g_switch_finish, because we
- // never return there.
- if (OwnedObject tracefunc = this->thread_state()->get_tracefunc()) {
- OwnedGreenlet trace_origin;
- trace_origin = origin_greenlet;
- try {
- g_calltrace(tracefunc,
- args ? mod_globs->event_switch : mod_globs->event_throw,
- trace_origin,
- this->_self);
- }
- catch (const PyErrOccurred&) {
- /* Turn trace errors into switch throws */
- args.CLEAR();
- }
- }
-
- // We no longer need the origin, it was only here for
- // tracing.
- // We may never actually exit this stack frame so we need
- // to explicitly clear it.
- // This could run Python code and switch.
- Py_CLEAR(origin_greenlet);
-
- OwnedObject result;
- if (!args) {
- /* pending exception */
- result = NULL;
- }
- else {
- /* call g.run(*args, **kwargs) */
- // This could result in further switches
- try {
- //result = run.PyCall(args.args(), args.kwargs());
- // CAUTION: Just invoking this, before the function even
- // runs, may cause memory allocations, which may trigger
- // GC, which may run arbitrary Python code.
- result = OwnedObject::consuming(PyObject_Call(run, args.args().borrow(), args.kwargs().borrow()));
- }
- catch (...) {
- // Unhandled C++ exception!
-
- // If we declare ourselves as noexcept, if we don't catch
- // this here, most platforms will just abort() the
- // process. But on 64-bit Windows with older versions of
- // the C runtime, this can actually corrupt memory and
- // just return. We see this when compiling with the
- // Windows 7.0 SDK targeting Windows Server 2008, but not
- // when using the Appveyor Visual Studio 2019 image. So
- // this currently only affects Python 2.7 on Windows 64.
- // That is, the tests pass and the runtime aborts
- // everywhere else.
- //
- // However, if we catch it and try to continue with a
- // Python error, then all Windows 64 bit platforms corrupt
- // memory. So all we can do is manually abort, hopefully
- // with a good error message. (Note that the above was
- // tested WITHOUT the `/EHr` switch being used at compile
- // time, so MSVC may have "optimized" out important
- // checking. Using that switch, we may be in a better
- // place in terms of memory corruption.) But sometimes it
- // can't be caught here at all, which is confusing but not
- // terribly surprising; so again, the G_NOEXCEPT_WIN32
- // plus "/EHr".
- //
- // Hopefully the basic C stdlib is still functional enough
- // for us to at least print an error.
- //
- // It gets more complicated than that, though, on some
- // platforms, specifically at least Linux/gcc/libstdc++. They use
- // an exception to unwind the stack when a background
- // thread exits. (See comments about noexcept.) So this
- // may not actually represent anything untoward. On those
- // platforms we allow throws of this to propagate, or
- // attempt to anyway.
-# if defined(WIN32) || defined(_WIN32)
- Py_FatalError(
- "greenlet: Unhandled C++ exception from a greenlet run function. "
- "Because memory is likely corrupted, terminating process.");
- std::abort();
-#else
- throw;
-#endif
- }
- }
- // These lines may run arbitrary code
- args.CLEAR();
- Py_CLEAR(run);
-
- if (!result
- && mod_globs->PyExc_GreenletExit.PyExceptionMatches()
- && (this->args())) {
- // This can happen, for example, if our only reference
- // goes away after we switch back to the parent.
- // See test_dealloc_switch_args_not_lost
- PyErrPieces clear_error;
- result <<= this->args();
- result = single_result(result);
- }
- this->release_args();
- this->python_state.did_finish(PyThreadState_GET());
-
- result = g_handle_exit(result);
- assert(this->thread_state()->borrow_current() == this->_self);
-
- /* jump back to parent */
- this->stack_state.set_inactive(); /* dead */
-
-
- // TODO: Can we decref some things here? Release our main greenlet
- // and maybe parent?
- for (Greenlet* parent = this->_parent;
- parent;
- parent = parent->parent()) {
- // We need to somewhere consume a reference to
- // the result; in most cases we'll never have control
- // back in this stack frame again. Calling
- // green_switch actually adds another reference!
- // This would probably be clearer with a specific API
- // to hand results to the parent.
- parent->args() <<= result;
- assert(!result);
- // The parent greenlet now owns the result; in the
- // typical case we'll never get back here to assign to
- // result and thus release the reference.
- try {
- result = parent->g_switch();
- }
- catch (const PyErrOccurred&) {
- // Ignore, keep passing the error on up.
- }
-
- /* Return here means switch to parent failed,
- * in which case we throw *current* exception
- * to the next parent in chain.
- */
- assert(!result);
- }
- /* We ran out of parents, cannot continue */
- PyErr_WriteUnraisable(this->self().borrow_o());
- Py_FatalError("greenlet: ran out of parent greenlets while propagating exception; "
- "cannot continue");
- std::abort();
-}
-
-void
-UserGreenlet::run(const BorrowedObject nrun)
-{
- if (this->started()) {
- throw AttributeError(
- "run cannot be set "
- "after the start of the greenlet");
- }
- this->_run_callable = nrun;
-}
-
-const OwnedGreenlet
-UserGreenlet::parent() const
-{
- return this->_parent;
-}
-
-void
-UserGreenlet::parent(const BorrowedObject raw_new_parent)
-{
- if (!raw_new_parent) {
- throw AttributeError("can't delete attribute");
- }
-
- BorrowedMainGreenlet main_greenlet_of_new_parent;
- BorrowedGreenlet new_parent(raw_new_parent.borrow()); // could
- // throw
- // TypeError!
- for (BorrowedGreenlet p = new_parent; p; p = p->parent()) {
- if (p == this->self()) {
- throw ValueError("cyclic parent chain");
- }
- main_greenlet_of_new_parent = p->main_greenlet();
- }
-
- if (!main_greenlet_of_new_parent) {
- throw ValueError("parent must not be garbage collected");
- }
-
- if (this->started()
- && this->_main_greenlet != main_greenlet_of_new_parent) {
- throw ValueError("parent cannot be on a different thread");
- }
-
- this->_parent = new_parent;
-}
-
-void
-UserGreenlet::murder_in_place()
-{
- this->_main_greenlet.CLEAR();
- Greenlet::murder_in_place();
-}
-
-bool
-UserGreenlet::belongs_to_thread(const ThreadState* thread_state) const
-{
- return Greenlet::belongs_to_thread(thread_state) && this->_main_greenlet == thread_state->borrow_main_greenlet();
-}
-
-
-int
-UserGreenlet::tp_traverse(visitproc visit, void* arg)
-{
- Py_VISIT(this->_parent.borrow_o());
- Py_VISIT(this->_main_greenlet.borrow_o());
- Py_VISIT(this->_run_callable.borrow_o());
-
- return Greenlet::tp_traverse(visit, arg);
-}
-
-int
-UserGreenlet::tp_clear()
-{
- Greenlet::tp_clear();
- this->_parent.CLEAR();
- this->_main_greenlet.CLEAR();
- this->_run_callable.CLEAR();
- return 0;
-}
-
-UserGreenlet::ParentIsCurrentGuard::ParentIsCurrentGuard(UserGreenlet* p,
- const ThreadState& thread_state)
- : oldparent(p->_parent),
- greenlet(p)
-{
- p->_parent = thread_state.get_current();
-}
-
-UserGreenlet::ParentIsCurrentGuard::~ParentIsCurrentGuard()
-{
- this->greenlet->_parent = oldparent;
- oldparent.CLEAR();
-}
-
-}; //namespace greenlet
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/__init__.py b/backend/.venv/lib/python3.12/site-packages/greenlet/__init__.py
deleted file mode 100644
index ed1cc17..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/__init__.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-The root of the greenlet package.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-__all__ = [
- '__version__',
- '_C_API',
-
- 'GreenletExit',
- 'error',
-
- 'getcurrent',
- 'greenlet',
-
- 'gettrace',
- 'settrace',
-]
-
-# pylint:disable=no-name-in-module
-
-###
-# Metadata
-###
-__version__ = '3.3.1'
-from ._greenlet import _C_API # pylint:disable=no-name-in-module
-
-###
-# Exceptions
-###
-from ._greenlet import GreenletExit
-from ._greenlet import error
-
-###
-# greenlets
-###
-from ._greenlet import getcurrent
-from ._greenlet import greenlet
-
-###
-# tracing
-###
-try:
- from ._greenlet import gettrace
- from ._greenlet import settrace
-except ImportError:
- # Tracing wasn't supported.
- # XXX: The option to disable it was removed in 1.0,
- # so this branch should be dead code.
- pass
-
-###
-# Constants
-# These constants aren't documented and aren't recommended.
-# In 1.0, USE_GC and USE_TRACING are always true, and USE_CONTEXT_VARS
-# is the same as ``sys.version_info[:2] >= 3.7``
-###
-from ._greenlet import GREENLET_USE_CONTEXT_VARS # pylint:disable=unused-import
-from ._greenlet import GREENLET_USE_GC # pylint:disable=unused-import
-from ._greenlet import GREENLET_USE_TRACING # pylint:disable=unused-import
-
-# Controlling the use of the gc module. Provisional API for this greenlet
-# implementation in 2.0.
-from ._greenlet import CLOCKS_PER_SEC # pylint:disable=unused-import
-from ._greenlet import enable_optional_cleanup # pylint:disable=unused-import
-from ._greenlet import get_clocks_used_doing_optional_cleanup # pylint:disable=unused-import
-
-# Other APIS in the _greenlet module are for test support.
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc b/backend/.venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index ff3dba1..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so b/backend/.venv/lib/python3.12/site-packages/greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so
deleted file mode 100755
index c495d26..0000000
Binary files a/backend/.venv/lib/python3.12/site-packages/greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so and /dev/null differ
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.cpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.cpp
deleted file mode 100644
index 7722bd0..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.cpp
+++ /dev/null
@@ -1,323 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-/* Format with:
- * clang-format -i --style=file src/greenlet/greenlet.c
- *
- *
- * Fix missing braces with:
- * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
-*/
-#include
-#include
-#include
-#include
-
-
-#define PY_SSIZE_T_CLEAN
-#include
-#include "structmember.h" // PyMemberDef
-
-#include "greenlet_internal.hpp"
-// Code after this point can assume access to things declared in stdint.h,
-// including the fixed-width types. This goes for the platform-specific switch functions
-// as well.
-#include "greenlet_refs.hpp"
-#include "greenlet_slp_switch.hpp"
-
-#include "greenlet_thread_support.hpp"
-#include "TGreenlet.hpp"
-
-#include "TGreenletGlobals.cpp"
-
-#include "TGreenlet.cpp"
-#include "TMainGreenlet.cpp"
-#include "TUserGreenlet.cpp"
-#include "TBrokenGreenlet.cpp"
-#include "TExceptionState.cpp"
-#include "TPythonState.cpp"
-#include "TStackState.cpp"
-
-#include "TThreadState.hpp"
-#include "TThreadStateCreator.hpp"
-#include "TThreadStateDestroy.cpp"
-
-#include "PyGreenlet.cpp"
-#include "PyGreenletUnswitchable.cpp"
-#include "CObjects.cpp"
-
-using greenlet::LockGuard;
-using greenlet::LockInitError;
-using greenlet::PyErrOccurred;
-using greenlet::Require;
-
-using greenlet::g_handle_exit;
-using greenlet::single_result;
-
-using greenlet::Greenlet;
-using greenlet::UserGreenlet;
-using greenlet::MainGreenlet;
-using greenlet::BrokenGreenlet;
-using greenlet::ThreadState;
-using greenlet::PythonState;
-
-
-
-// ******* Implementation of things from included files
-template
-greenlet::refs::_BorrowedGreenlet& greenlet::refs::_BorrowedGreenlet::operator=(const greenlet::refs::BorrowedObject& other)
-{
- this->_set_raw_pointer(static_cast(other));
- return *this;
-}
-
-template
-inline greenlet::refs::_BorrowedGreenlet::operator Greenlet*() const noexcept
-{
- if (!this->p) {
- return nullptr;
- }
- return reinterpret_cast(this->p)->pimpl;
-}
-
-template
-greenlet::refs::_BorrowedGreenlet::_BorrowedGreenlet(const BorrowedObject& p)
- : BorrowedReference(nullptr)
-{
-
- this->_set_raw_pointer(p.borrow());
-}
-
-template
-inline greenlet::refs::_OwnedGreenlet::operator Greenlet*() const noexcept
-{
- if (!this->p) {
- return nullptr;
- }
- return reinterpret_cast(this->p)->pimpl;
-}
-
-
-
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wmissing-field-initializers"
-# pragma clang diagnostic ignored "-Wwritable-strings"
-#elif defined(__GNUC__)
-# pragma GCC diagnostic push
-// warning: ISO C++ forbids converting a string constant to ‘char*’
-// (The python APIs aren't const correct and accept writable char*)
-# pragma GCC diagnostic ignored "-Wwrite-strings"
-#endif
-
-
-/***********************************************************
-
-A PyGreenlet is a range of C stack addresses that must be
-saved and restored in such a way that the full range of the
-stack contains valid data when we switch to it.
-
-Stack layout for a greenlet:
-
- | ^^^ |
- | older data |
- | |
- stack_stop . |_______________|
- . | |
- . | greenlet data |
- . | in stack |
- . * |_______________| . . _____________ stack_copy + stack_saved
- . | | | |
- . | data | |greenlet data|
- . | unrelated | | saved |
- . | to | | in heap |
- stack_start . | this | . . |_____________| stack_copy
- | greenlet |
- | |
- | newer data |
- | vvv |
-
-
-Note that a greenlet's stack data is typically partly at its correct
-place in the stack, and partly saved away in the heap, but always in
-the above configuration: two blocks, the more recent one in the heap
-and the older one still in the stack (either block may be empty).
-
-Greenlets are chained: each points to the previous greenlet, which is
-the one that owns the data currently in the C stack above my
-stack_stop. The currently running greenlet is the first element of
-this chain. The main (initial) greenlet is the last one. Greenlets
-whose stack is entirely in the heap can be skipped from the chain.
-
-The chain is not related to execution order, but only to the order
-in which bits of C stack happen to belong to greenlets at a particular
-point in time.
-
-The main greenlet doesn't have a stack_stop: it is responsible for the
-complete rest of the C stack, and we don't know where it begins. We
-use (char*) -1, the largest possible address.
-
-States:
- stack_stop == NULL && stack_start == NULL: did not start yet
- stack_stop != NULL && stack_start == NULL: already finished
- stack_stop != NULL && stack_start != NULL: active
-
-The running greenlet's stack_start is undefined but not NULL.
-
- ***********************************************************/
-
-
-
-
-/***********************************************************/
-
-/* Some functions must not be inlined:
- * slp_restore_state, when inlined into slp_switch might cause
- it to restore stack over its own local variables
- * slp_save_state, when inlined would add its own local
- variables to the saved stack, wasting space
- * slp_switch, cannot be inlined for obvious reasons
- * g_initialstub, when inlined would receive a pointer into its
- own stack frame, leading to incomplete stack save/restore
-
-g_initialstub is a member function and declared virtual so that the
-compiler always calls it through a vtable.
-
-slp_save_state and slp_restore_state are also member functions. They
-are called from trampoline functions that themselves are declared as
-not eligible for inlining.
-*/
-
-extern "C" {
-static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref)
-{
- return switching_thread_state->slp_save_state(stackref);
-}
-static void GREENLET_NOINLINE(slp_restore_state_trampoline)()
-{
- switching_thread_state->slp_restore_state();
-}
-}
-
-
-/***********************************************************/
-
-
-#include "PyModule.cpp"
-
-
-
-static PyObject*
-greenlet_internal_mod_init() noexcept
-{
- static void* _PyGreenlet_API[PyGreenlet_API_pointers];
-
- try {
- CreatedModule m(greenlet_module_def);
-
- Require(PyType_Ready(&PyGreenlet_Type));
- Require(PyType_Ready(&PyGreenletUnswitchable_Type));
-
- mod_globs = new greenlet::GreenletGlobals;
- ThreadState::init();
-
- m.PyAddObject("greenlet", PyGreenlet_Type);
- m.PyAddObject("UnswitchableGreenlet", PyGreenletUnswitchable_Type);
- m.PyAddObject("error", mod_globs->PyExc_GreenletError);
- m.PyAddObject("GreenletExit", mod_globs->PyExc_GreenletExit);
-
- m.PyAddObject("GREENLET_USE_GC", 1);
- m.PyAddObject("GREENLET_USE_TRACING", 1);
- m.PyAddObject("GREENLET_USE_CONTEXT_VARS", 1L);
- m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L);
-
- OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC));
- m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec);
-
- /* also publish module-level data as attributes of the greentype. */
- // XXX: This is weird, and enables a strange pattern of
- // confusing the class greenlet with the module greenlet; with
- // the exception of (possibly) ``getcurrent()``, this
- // shouldn't be encouraged so don't add new items here.
- for (const char* const* p = copy_on_greentype; *p; p++) {
- OwnedObject o = m.PyRequireAttr(*p);
- PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o.borrow());
- }
-
- /*
- * Expose C API
- */
-
- /* types */
- _PyGreenlet_API[PyGreenlet_Type_NUM] = (void*)&PyGreenlet_Type;
-
- /* exceptions */
- _PyGreenlet_API[PyExc_GreenletError_NUM] = (void*)mod_globs->PyExc_GreenletError;
- _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void*)mod_globs->PyExc_GreenletExit;
-
- /* methods */
- _PyGreenlet_API[PyGreenlet_New_NUM] = (void*)PyGreenlet_New;
- _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void*)PyGreenlet_GetCurrent;
- _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void*)PyGreenlet_Throw;
- _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void*)PyGreenlet_Switch;
- _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void*)PyGreenlet_SetParent;
-
- /* Previously macros, but now need to be functions externally. */
- _PyGreenlet_API[PyGreenlet_MAIN_NUM] = (void*)Extern_PyGreenlet_MAIN;
- _PyGreenlet_API[PyGreenlet_STARTED_NUM] = (void*)Extern_PyGreenlet_STARTED;
- _PyGreenlet_API[PyGreenlet_ACTIVE_NUM] = (void*)Extern_PyGreenlet_ACTIVE;
- _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM] = (void*)Extern_PyGreenlet_GET_PARENT;
-
- /* XXX: Note that our module name is ``greenlet._greenlet``, but for
- backwards compatibility with existing C code, we need the _C_API to
- be directly in greenlet.
- */
- const NewReference c_api_object(Require(
- PyCapsule_New(
- (void*)_PyGreenlet_API,
- "greenlet._C_API",
- NULL)));
- m.PyAddObject("_C_API", c_api_object);
- assert(c_api_object.REFCNT() == 2);
-
- // cerr << "Sizes:"
- // << "\n\tGreenlet : " << sizeof(Greenlet)
- // << "\n\tUserGreenlet : " << sizeof(UserGreenlet)
- // << "\n\tMainGreenlet : " << sizeof(MainGreenlet)
- // << "\n\tExceptionState : " << sizeof(greenlet::ExceptionState)
- // << "\n\tPythonState : " << sizeof(greenlet::PythonState)
- // << "\n\tStackState : " << sizeof(greenlet::StackState)
- // << "\n\tSwitchingArgs : " << sizeof(greenlet::SwitchingArgs)
- // << "\n\tOwnedObject : " << sizeof(greenlet::refs::OwnedObject)
- // << "\n\tBorrowedObject : " << sizeof(greenlet::refs::BorrowedObject)
- // << "\n\tPyGreenlet : " << sizeof(PyGreenlet)
- // << endl;
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m.borrow(), Py_MOD_GIL_NOT_USED);
-#endif
- return m.borrow(); // But really it's the main reference.
- }
- catch (const LockInitError& e) {
- PyErr_SetString(PyExc_MemoryError, e.what());
- return NULL;
- }
- catch (const PyErrOccurred&) {
- return NULL;
- }
-
-}
-
-extern "C" {
-
-PyMODINIT_FUNC
-PyInit__greenlet(void)
-{
- return greenlet_internal_mod_init();
-}
-
-}; // extern C
-
-#ifdef __clang__
-# pragma clang diagnostic pop
-#elif defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.h b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.h
deleted file mode 100644
index d02a16e..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-
-/* Greenlet object interface */
-
-#ifndef Py_GREENLETOBJECT_H
-#define Py_GREENLETOBJECT_H
-
-
-#include
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* This is deprecated and undocumented. It does not change. */
-#define GREENLET_VERSION "1.0.0"
-
-#ifndef GREENLET_MODULE
-#define implementation_ptr_t void*
-#endif
-
-typedef struct _greenlet {
- PyObject_HEAD
- PyObject* weakreflist;
- PyObject* dict;
- implementation_ptr_t pimpl;
-} PyGreenlet;
-
-#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
-
-
-/* C API functions */
-
-/* Total number of symbols that are exported */
-#define PyGreenlet_API_pointers 12
-
-#define PyGreenlet_Type_NUM 0
-#define PyExc_GreenletError_NUM 1
-#define PyExc_GreenletExit_NUM 2
-
-#define PyGreenlet_New_NUM 3
-#define PyGreenlet_GetCurrent_NUM 4
-#define PyGreenlet_Throw_NUM 5
-#define PyGreenlet_Switch_NUM 6
-#define PyGreenlet_SetParent_NUM 7
-
-#define PyGreenlet_MAIN_NUM 8
-#define PyGreenlet_STARTED_NUM 9
-#define PyGreenlet_ACTIVE_NUM 10
-#define PyGreenlet_GET_PARENT_NUM 11
-
-#ifndef GREENLET_MODULE
-/* This section is used by modules that uses the greenlet C API */
-static void** _PyGreenlet_API = NULL;
-
-# define PyGreenlet_Type \
- (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
-
-# define PyExc_GreenletError \
- ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
-
-# define PyExc_GreenletExit \
- ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
-
-/*
- * PyGreenlet_New(PyObject *args)
- *
- * greenlet.greenlet(run, parent=None)
- */
-# define PyGreenlet_New \
- (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
- _PyGreenlet_API[PyGreenlet_New_NUM])
-
-/*
- * PyGreenlet_GetCurrent(void)
- *
- * greenlet.getcurrent()
- */
-# define PyGreenlet_GetCurrent \
- (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
-
-/*
- * PyGreenlet_Throw(
- * PyGreenlet *greenlet,
- * PyObject *typ,
- * PyObject *val,
- * PyObject *tb)
- *
- * g.throw(...)
- */
-# define PyGreenlet_Throw \
- (*(PyObject * (*)(PyGreenlet * self, \
- PyObject * typ, \
- PyObject * val, \
- PyObject * tb)) \
- _PyGreenlet_API[PyGreenlet_Throw_NUM])
-
-/*
- * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
- *
- * g.switch(*args, **kwargs)
- */
-# define PyGreenlet_Switch \
- (*(PyObject * \
- (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
- _PyGreenlet_API[PyGreenlet_Switch_NUM])
-
-/*
- * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
- *
- * g.parent = new_parent
- */
-# define PyGreenlet_SetParent \
- (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
- _PyGreenlet_API[PyGreenlet_SetParent_NUM])
-
-/*
- * PyGreenlet_GetParent(PyObject* greenlet)
- *
- * return greenlet.parent;
- *
- * This could return NULL even if there is no exception active.
- * If it does not return NULL, you are responsible for decrementing the
- * reference count.
- */
-# define PyGreenlet_GetParent \
- (*(PyGreenlet* (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
-
-/*
- * deprecated, undocumented alias.
- */
-# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
-
-# define PyGreenlet_MAIN \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_MAIN_NUM])
-
-# define PyGreenlet_STARTED \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_STARTED_NUM])
-
-# define PyGreenlet_ACTIVE \
- (*(int (*)(PyGreenlet*)) \
- _PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
-
-
-
-
-/* Macro that imports greenlet and initializes C API */
-/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
- keep the older definition to be sure older code that might have a copy of
- the header still works. */
-# define PyGreenlet_Import() \
- { \
- _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
- }
-
-#endif /* GREENLET_MODULE */
-
-#ifdef __cplusplus
-}
-#endif
-#endif /* !Py_GREENLETOBJECT_H */
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp
deleted file mode 100644
index 1cd9223..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-#ifndef GREENLET_ALLOCATOR_HPP
-#define GREENLET_ALLOCATOR_HPP
-
-#define PY_SSIZE_T_CLEAN
-#include
-#include
-#include "greenlet_compiler_compat.hpp"
-#include "greenlet_cpython_compat.hpp"
-
-
-namespace greenlet
-{
- // This allocator is stateless; all instances are identical.
- // It can *ONLY* be used when we're sure we're holding the GIL
- // (Python's allocators require the GIL).
- template
- struct PythonAllocator : public std::allocator {
-
- PythonAllocator(const PythonAllocator& UNUSED(other))
- : std::allocator()
- {
- }
-
- PythonAllocator(const std::allocator other)
- : std::allocator(other)
- {}
-
- template
- PythonAllocator(const std::allocator& other)
- : std::allocator(other)
- {
- }
-
- PythonAllocator() : std::allocator() {}
-
- T* allocate(size_t number_objects, const void* UNUSED(hint)=0)
- {
- void* p;
- if (number_objects == 1) {
-#ifdef Py_GIL_DISABLED
- p = PyMem_Malloc(sizeof(T) * number_objects);
-#else
- p = PyObject_Malloc(sizeof(T));
-#endif
- }
- else {
- p = PyMem_Malloc(sizeof(T) * number_objects);
- }
- return static_cast(p);
- }
-
- void deallocate(T* t, size_t n)
- {
- void* p = t;
- if (n == 1) {
-#ifdef Py_GIL_DISABLED
- PyMem_Free(p);
-#else
- PyObject_Free(p);
-#endif
- }
- else {
- PyMem_Free(p);
- }
- }
- // This member is deprecated in C++17 and removed in C++20
- template< class U >
- struct rebind {
- typedef PythonAllocator other;
- };
-
- };
-
-}
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp
deleted file mode 100644
index af24bd8..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-#ifndef GREENLET_COMPILER_COMPAT_HPP
-#define GREENLET_COMPILER_COMPAT_HPP
-
-/**
- * Definitions to aid with compatibility with different compilers.
- *
- * .. caution:: Use extreme care with noexcept.
- * Some compilers and runtimes, specifically gcc/libgcc/libstdc++ on
- * Linux, implement stack unwinding by throwing an uncatchable
- * exception, one that specifically does not appear to be an active
- * exception to the rest of the runtime. If this happens while we're in a noexcept function,
- * we have violated our dynamic exception contract, and so the runtime
- * will call std::terminate(), which kills the process with the
- * unhelpful message "terminate called without an active exception".
- *
- * This has happened in this scenario: A background thread is running
- * a greenlet that has made a native call and released the GIL.
- * Meanwhile, the main thread finishes and starts shutting down the
- * interpreter. When the background thread is scheduled again and
- * attempts to obtain the GIL, it notices that the interpreter is
- * exiting and calls ``pthread_exit()``. This in turn starts to unwind
- * the stack by throwing that exception. But we had the ``PyCall``
- * functions annotated as noexcept, so the runtime terminated us.
- *
- * #2 0x00007fab26fec2b7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
- * #3 0x00007fab26febb3c in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6
- * #4 0x00007fab26f34de6 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
- * #6 0x00007fab276a34c6 in __GI___pthread_unwind at ./nptl/unwind.c:130
- * #7 0x00007fab2769bd3a in __do_cancel () at ../sysdeps/nptl/pthreadP.h:280
- * #8 __GI___pthread_exit (value=value@entry=0x0) at ./nptl/pthread_exit.c:36
- * #9 0x000000000052e567 in PyThread_exit_thread () at ../Python/thread_pthread.h:370
- * #10 0x00000000004d60b5 in take_gil at ../Python/ceval_gil.h:224
- * #11 0x00000000004d65f9 in PyEval_RestoreThread at ../Python/ceval.c:467
- * #12 0x000000000060cce3 in setipaddr at ../Modules/socketmodule.c:1203
- * #13 0x00000000006101cd in socket_gethostbyname
- */
-
-#include
-
-# define G_NO_COPIES_OF_CLS(Cls) private: \
- Cls(const Cls& other) = delete; \
- Cls& operator=(const Cls& other) = delete
-
-# define G_NO_ASSIGNMENT_OF_CLS(Cls) private: \
- Cls& operator=(const Cls& other) = delete
-
-# define G_NO_COPY_CONSTRUCTOR_OF_CLS(Cls) private: \
- Cls(const Cls& other) = delete;
-
-
-// CAUTION: MSVC is stupidly picky:
-//
-// "The compiler ignores, without warning, any __declspec keywords
-// placed after * or & and in front of the variable identifier in a
-// declaration."
-// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160)
-//
-// So pointer return types must be handled differently (because of the
-// trailing *), or you get inscrutable compiler warnings like "error
-// C2059: syntax error: ''"
-//
-// In C++ 11, there is a standard syntax for attributes, and
-// GCC defines an attribute to use with this: [[gnu:noinline]].
-// In the future, this is expected to become standard.
-
-#if defined(__GNUC__) || defined(__clang__)
-/* We used to check for GCC 4+ or 3.4+, but those compilers are
- laughably out of date. Just assume they support it. */
-# define GREENLET_NOINLINE(name) __attribute__((noinline)) name
-# define GREENLET_NOINLINE_P(rtype, name) rtype __attribute__((noinline)) name
-# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
-#elif defined(_MSC_VER)
-/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */
-# define GREENLET_NOINLINE(name) __declspec(noinline) name
-# define GREENLET_NOINLINE_P(rtype, name) __declspec(noinline) rtype name
-# define UNUSED(x) UNUSED_ ## x
-#endif
-
-#if defined(_MSC_VER)
-# define G_NOEXCEPT_WIN32 noexcept
-#else
-# define G_NOEXCEPT_WIN32
-#endif
-
-#if defined(__GNUC__) && defined(__POWERPC__) && defined(__APPLE__)
-// 32-bit PPC/MacOSX. Only known to be tested on unreleased versions
-// of macOS 10.6 using a macports build gcc 14. It appears that
-// running C++ destructors of thread-local variables is broken.
-
-// See https://github.com/python-greenlet/greenlet/pull/419
-# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 1
-#else
-# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 0
-#endif
-
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp
deleted file mode 100644
index f46d977..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-#ifndef GREENLET_CPYTHON_COMPAT_H
-#define GREENLET_CPYTHON_COMPAT_H
-
-/**
- * Helpers for compatibility with multiple versions of CPython.
- */
-
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-
-
-#if PY_VERSION_HEX >= 0x30A00B1
-# define GREENLET_PY310 1
-#else
-# define GREENLET_PY310 0
-#endif
-
-/*
-Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member.
-See https://github.com/python/cpython/pull/25276
-We have to save and restore this as well.
-
-Python 3.13 removed PyThreadState.cframe (GH-108035).
-*/
-#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000
-# define GREENLET_USE_CFRAME 1
-#else
-# define GREENLET_USE_CFRAME 0
-#endif
-
-
-#if PY_VERSION_HEX >= 0x30B00A4
-/*
-Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
-https://bugs.python.org/issue46090). Summary of breaking internal changes:
-- Python 3.11 alpha 1 changed how frame objects are represented internally.
- - https://github.com/python/cpython/pull/30122
-- Python 3.11 alpha 3 changed how recursion limits are stored.
- - https://github.com/python/cpython/pull/29524
-- Python 3.11 alpha 4 changed how exception state is stored. It also includes a
- change to help greenlet save and restore the interpreter frame "data stack".
- - https://github.com/python/cpython/pull/30122
- - https://github.com/python/cpython/pull/30234
-*/
-# define GREENLET_PY311 1
-#else
-# define GREENLET_PY311 0
-#endif
-
-
-#if PY_VERSION_HEX >= 0x30C0000
-# define GREENLET_PY312 1
-#else
-# define GREENLET_PY312 0
-#endif
-
-#if PY_VERSION_HEX >= 0x30D0000
-# define GREENLET_PY313 1
-#else
-# define GREENLET_PY313 0
-#endif
-
-#if PY_VERSION_HEX >= 0x30E0000
-# define GREENLET_PY314 1
-#else
-# define GREENLET_PY314 0
-#endif
-
-#if PY_VERSION_HEX >= 0x30F0000
-# define GREENLET_PY315 1
-#else
-# define GREENLET_PY315 0
-#endif
-
-#ifndef Py_SET_REFCNT
-/* Py_REFCNT and Py_SIZE macros are converted to functions
-https://bugs.python.org/issue39573 */
-# define Py_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt)
-#endif
-
-#ifdef _Py_DEC_REFTOTAL
-# define GREENLET_Py_DEC_REFTOTAL _Py_DEC_REFTOTAL
-#else
-/* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by:
- https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924
-
- The symbol we use to replace it was removed by at least 3.12.
-*/
-# ifdef Py_REF_DEBUG
-# if GREENLET_PY312
-# define GREENLET_Py_DEC_REFTOTAL
-# else
-# define GREENLET_Py_DEC_REFTOTAL _Py_RefTotal--
-# endif
-# else
-# define GREENLET_Py_DEC_REFTOTAL
-# endif
-#endif
-// Define these flags like Cython does if we're on an old version.
-#ifndef Py_TPFLAGS_CHECKTYPES
- #define Py_TPFLAGS_CHECKTYPES 0
-#endif
-#ifndef Py_TPFLAGS_HAVE_INDEX
- #define Py_TPFLAGS_HAVE_INDEX 0
-#endif
-#ifndef Py_TPFLAGS_HAVE_NEWBUFFER
- #define Py_TPFLAGS_HAVE_NEWBUFFER 0
-#endif
-
-#ifndef Py_TPFLAGS_HAVE_VERSION_TAG
- #define Py_TPFLAGS_HAVE_VERSION_TAG 0
-#endif
-
-#define G_TPFLAGS_DEFAULT Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC
-
-
-#if PY_VERSION_HEX < 0x03090000
-// The official version only became available in 3.9
-# define PyObject_GC_IsTracked(o) _PyObject_GC_IS_TRACKED(o)
-#endif
-
-
-// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2
-#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
-static inline void PyThreadState_EnterTracing(PyThreadState *tstate)
-{
- tstate->tracing++;
-#if PY_VERSION_HEX >= 0x030A00A1
- tstate->cframe->use_tracing = 0;
-#else
- tstate->use_tracing = 0;
-#endif
-}
-#endif
-
-// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2
-#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
-static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
-{
- tstate->tracing--;
- int use_tracing = (tstate->c_tracefunc != NULL
- || tstate->c_profilefunc != NULL);
-#if PY_VERSION_HEX >= 0x030A00A1
- tstate->cframe->use_tracing = use_tracing;
-#else
- tstate->use_tracing = use_tracing;
-#endif
-}
-#endif
-
-#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT)
-# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT
-#endif
-
-#endif /* GREENLET_CPYTHON_COMPAT_H */
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp
deleted file mode 100644
index 617f07c..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp
+++ /dev/null
@@ -1,171 +0,0 @@
-#ifndef GREENLET_EXCEPTIONS_HPP
-#define GREENLET_EXCEPTIONS_HPP
-
-#define PY_SSIZE_T_CLEAN
-#include
-#include
-#include
-
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wunused-function"
-#endif
-
-namespace greenlet {
-
- class PyErrOccurred : public std::runtime_error
- {
- public:
-
- // CAUTION: In debug builds, may run arbitrary Python code.
- static const PyErrOccurred
- from_current()
- {
- assert(PyErr_Occurred());
-#ifndef NDEBUG
- // This is not exception safe, and
- // not necessarily safe in general (what if it switches?)
- // But we only do this in debug mode, where we are in
- // tight control of what exceptions are getting raised and
- // can prevent those issues.
-
- // You can't call PyObject_Str with a pending exception.
- PyObject* typ;
- PyObject* val;
- PyObject* tb;
-
- PyErr_Fetch(&typ, &val, &tb);
- PyObject* typs = PyObject_Str(typ);
- PyObject* vals = PyObject_Str(val ? val : typ);
- const char* typ_msg = PyUnicode_AsUTF8(typs);
- const char* val_msg = PyUnicode_AsUTF8(vals);
- PyErr_Restore(typ, val, tb);
-
- std::string msg(typ_msg);
- msg += ": ";
- msg += val_msg;
- PyErrOccurred ex(msg);
- Py_XDECREF(typs);
- Py_XDECREF(vals);
-
- return ex;
-#else
- return PyErrOccurred();
-#endif
- }
-
- PyErrOccurred() : std::runtime_error("")
- {
- assert(PyErr_Occurred());
- }
-
- PyErrOccurred(const std::string& msg) : std::runtime_error(msg)
- {
- assert(PyErr_Occurred());
- }
-
- PyErrOccurred(PyObject* exc_kind, const char* const msg)
- : std::runtime_error(msg)
- {
- PyErr_SetString(exc_kind, msg);
- }
-
- PyErrOccurred(PyObject* exc_kind, const std::string msg)
- : std::runtime_error(msg)
- {
- // This copies the c_str, so we don't have any lifetime
- // issues to worry about.
- PyErr_SetString(exc_kind, msg.c_str());
- }
-
- PyErrOccurred(PyObject* exc_kind,
- const std::string msg, //This is the format
- //string; that's not
- //usually safe!
-
- PyObject* borrowed_obj_one, PyObject* borrowed_obj_two)
- : std::runtime_error(msg)
- {
-
- //This is designed specifically for the
- //``check_switch_allowed`` function.
-
- // PyObject_Str and PyObject_Repr are safe to call with
- // NULL pointers; they return the string "" in that
- // case.
- // This function always returns null.
- PyErr_Format(exc_kind,
- msg.c_str(),
- borrowed_obj_one, borrowed_obj_two);
- }
- };
-
- class TypeError : public PyErrOccurred
- {
- public:
- TypeError(const char* const what)
- : PyErrOccurred(PyExc_TypeError, what)
- {
- }
- TypeError(const std::string what)
- : PyErrOccurred(PyExc_TypeError, what)
- {
- }
- };
-
- class ValueError : public PyErrOccurred
- {
- public:
- ValueError(const char* const what)
- : PyErrOccurred(PyExc_ValueError, what)
- {
- }
- };
-
- class AttributeError : public PyErrOccurred
- {
- public:
- AttributeError(const char* const what)
- : PyErrOccurred(PyExc_AttributeError, what)
- {
- }
- };
-
- /**
- * Calls `Py_FatalError` when constructed, so you can't actually
- * throw this. It just makes static analysis easier.
- */
- class PyFatalError : public std::runtime_error
- {
- public:
- PyFatalError(const char* const msg)
- : std::runtime_error(msg)
- {
- Py_FatalError(msg);
- }
- };
-
- static inline PyObject*
- Require(PyObject* p, const std::string& msg="")
- {
- if (!p) {
- throw PyErrOccurred(msg);
- }
- return p;
- };
-
- static inline void
- Require(const int retval)
- {
- if (retval < 0) {
- throw PyErrOccurred();
- }
- };
-
-
-};
-#ifdef __clang__
-# pragma clang diagnostic pop
-#endif
-
-#endif
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp
deleted file mode 100644
index f2b15d5..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
-#ifndef GREENLET_INTERNAL_H
-#define GREENLET_INTERNAL_H
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wunused-function"
-#endif
-
-/**
- * Implementation helpers.
- *
- * C++ templates and inline functions should go here.
- */
-#define PY_SSIZE_T_CLEAN
-#include "greenlet_compiler_compat.hpp"
-#include "greenlet_cpython_compat.hpp"
-#include "greenlet_exceptions.hpp"
-#include "TGreenlet.hpp"
-#include "greenlet_allocator.hpp"
-
-#include
-#include
-
-#define GREENLET_MODULE
-struct _greenlet;
-typedef struct _greenlet PyGreenlet;
-namespace greenlet {
-
- class ThreadState;
- // We can't use the PythonAllocator for this, because we push to it
- // from the thread state destructor, which doesn't have the GIL,
- // and Python's allocators can only be called with the GIL.
- typedef std::vector cleanup_queue_t;
-
-};
-
-
-#define implementation_ptr_t greenlet::Greenlet*
-
-
-#include "greenlet.h"
-
-void
-greenlet::refs::MainGreenletExactChecker(void *p)
-{
- if (!p) {
- return;
- }
- // We control the class of the main greenlet exactly.
- if (Py_TYPE(p) != &PyGreenlet_Type) {
- std::string err("MainGreenlet: Expected exactly a greenlet, not a ");
- err += Py_TYPE(p)->tp_name;
- throw greenlet::TypeError(err);
- }
-
- // Greenlets from dead threads no longer respond to main() with a
- // true value; so in that case we need to perform an additional
- // check.
- Greenlet* g = static_cast(p)->pimpl;
- if (g->main()) {
- return;
- }
- if (!dynamic_cast(g)) {
- std::string err("MainGreenlet: Expected exactly a main greenlet, not a ");
- err += Py_TYPE(p)->tp_name;
- throw greenlet::TypeError(err);
- }
-}
-
-
-
-template
-inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet::operator->() const noexcept
-{
- return reinterpret_cast(this->p)->pimpl;
-}
-
-template
-inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet::operator->() const noexcept
-{
- return reinterpret_cast(this->p)->pimpl;
-}
-
-#include
-#include
-
-
-extern PyTypeObject PyGreenlet_Type;
-
-
-
-/**
- * Forward declarations needed in multiple files.
- */
-static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs);
-
-
-#ifdef __clang__
-# pragma clang diagnostic pop
-#endif
-
-
-#endif
-
-// Local Variables:
-// flycheck-clang-include-path: ("../../include" "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10")
-// End:
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp
deleted file mode 100644
index 9635a1b..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp
+++ /dev/null
@@ -1,100 +0,0 @@
-#ifndef GREENLET_MSVC_COMPAT_HPP
-#define GREENLET_MSVC_COMPAT_HPP
-/*
- * Support for MSVC on Windows.
- *
- * Beginning with Python 3.14, some of the internal
- * include files we need are not compatible with MSVC
- * in C++ mode:
- *
- * internal\pycore_stackref.h(253): error C4576: a parenthesized type
- * followed by an initializer list is a non-standard explicit type conversion syntax
- *
- * This file is included from ``internal/pycore_interpframe.h``, which
- * we need for the ``_PyFrame_IsIncomplete`` API.
- *
- * Unfortunately, that API is a ``static inline`` function, as are a
- * bunch of the functions it calls. The only solution seems to be to
- * copy those definitions and the supporting inline functions here.
- *
- * Now, this makes us VERY fragile to changes in those functions. Because
- * they're internal and static, the CPython devs might feel free to change
- * them in even minor versions, meaning that we could runtime link and load,
- * but still crash. We have that problem on all platforms though. It's just worse
- * here because we have to keep copying the updated definitions.
- */
-#include
-#include "greenlet_cpython_compat.hpp"
-
-// This file is only included on 3.14+
-
-extern "C" {
-
-// pycore_code.h ----------------
-#define _PyCode_CODE(CO) _Py_RVALUE((_Py_CODEUNIT *)(CO)->co_code_adaptive)
-
-#ifdef Py_GIL_DISABLED
-static inline _PyCodeArray *
-_PyCode_GetTLBCArray(PyCodeObject *co)
-{
- return _Py_STATIC_CAST(_PyCodeArray *,
- _Py_atomic_load_ptr_acquire(&co->co_tlbc));
-}
-#endif
-// End pycore_code.h ----------
-
-// pycore_interpframe.h ----------
-#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
-
-#define Py_TAG_BITS 0
-#else
-#define Py_TAG_BITS ((uintptr_t)1)
-#define Py_TAG_DEFERRED (1)
-#endif
-
-
-static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
-#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits)
-
-static inline PyObject *
-PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)
-{
- PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS)));
- return cleared;
-}
-
-static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
- assert(!PyStackRef_IsNull(f->f_executable));
- PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
- assert(PyCode_Check(executable));
- return (PyCodeObject *)executable;
-}
-
-
-static inline _Py_CODEUNIT *
-_PyFrame_GetBytecode(_PyInterpreterFrame *f)
-{
-#ifdef Py_GIL_DISABLED
- PyCodeObject *co = _PyFrame_GetCode(f);
- _PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
- assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
- return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index];
-#else
- return _PyCode_CODE(_PyFrame_GetCode(f));
-#endif
-}
-
-static inline bool //_Py_NO_SANITIZE_THREAD
-_PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
-{
- if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
- return true;
- }
- return frame->owner != FRAME_OWNED_BY_GENERATOR &&
- frame->instr_ptr < _PyFrame_GetBytecode(frame) +
- _PyFrame_GetCode(frame)->_co_firsttraceable;
-}
-// pycore_interpframe.h ----------
-
-}
-#endif // GREENLET_MSVC_COMPAT_HPP
diff --git a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp b/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp
deleted file mode 100644
index b7e5e3f..0000000
--- a/backend/.venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp
+++ /dev/null
@@ -1,1118 +0,0 @@
-#ifndef GREENLET_REFS_HPP
-#define GREENLET_REFS_HPP
-
-#define PY_SSIZE_T_CLEAN
-#include
-
-#include
-
-//#include "greenlet_internal.hpp"
-#include "greenlet_compiler_compat.hpp"
-#include "greenlet_cpython_compat.hpp"
-#include "greenlet_exceptions.hpp"
-
-struct _greenlet;
-struct _PyMainGreenlet;
-
-typedef struct _greenlet PyGreenlet;
-extern PyTypeObject PyGreenlet_Type;
-
-
-#ifdef GREENLET_USE_STDIO
-#include
-using std::cerr;
-using std::endl;
-#endif
-
-namespace greenlet
-{
- class Greenlet;
-
- namespace refs
- {
- // Type checkers throw a TypeError if the argument is not
- // null, and isn't of the required Python type.
- // (We can't use most of the defined type checkers
- // like PyList_Check, etc, directly, because they are
- // implemented as macros.)
- typedef void (*TypeChecker)(void*);
-
- void
- NoOpChecker(void*)
- {
- return;
- }
-
- void
- GreenletChecker(void *p)
- {
- if (!p) {
- return;
- }
-
- PyTypeObject* typ = Py_TYPE(p);
- // fast, common path. (PyObject_TypeCheck is a macro or
- // static inline function, and it also does a
- // direct comparison of the type pointers, but its fast
- // path only handles one type)
- if (typ == &PyGreenlet_Type) {
- return;
- }
-
- if (!PyObject_TypeCheck(p, &PyGreenlet_Type)) {
- std::string err("GreenletChecker: Expected any type of greenlet, not ");
- err += Py_TYPE(p)->tp_name;
- throw TypeError(err);
- }
- }
-
- void
- MainGreenletExactChecker(void *p);
-
- template
- class PyObjectPointer;
-
- template
- class OwnedReference;
-
-
- template
- class BorrowedReference;
-
- typedef BorrowedReference BorrowedObject;
- typedef OwnedReference OwnedObject;
-
- class ImmortalObject;
- class ImmortalString;
-
- template
- class _OwnedGreenlet;
-
- typedef _OwnedGreenlet OwnedGreenlet;
- typedef _OwnedGreenlet OwnedMainGreenlet;
-
- template
- class _BorrowedGreenlet;
-
- typedef _BorrowedGreenlet BorrowedGreenlet;
-
- void
- ContextExactChecker(void *p)
- {
- if (!p) {
- return;
- }
- if (!PyContext_CheckExact(p)) {
- throw TypeError(
- "greenlet context must be a contextvars.Context or None"
- );
- }
- }
-
- typedef OwnedReference OwnedContext;
- }
-}
-
-namespace greenlet {
-
-
- namespace refs {
- // A set of classes to make reference counting rules in python
- // code explicit.
- //
- // Rules of use:
- // (1) Functions returning a new reference that the caller of the
- // function is expected to dispose of should return a
- // ``OwnedObject`` object. This object automatically releases its
- // reference when it goes out of scope. It works like a ``std::shared_ptr``
- // and can be copied or used as a function parameter (but don't do
- // that). Note that constructing a ``OwnedObject`` from a
- // PyObject* steals the reference.
- // (2) Parameters to functions should be either a
- // ``OwnedObject&``, or, more generally, a ``PyObjectPointer&``.
- // If the function needs to create its own new reference, it can
- // do so by copying to a local ``OwnedObject``.
- // (3) Functions returning an existing pointer that is NOT
- // incref'd, and which the caller MUST NOT decref,
- // should return a ``BorrowedObject``.
-
- // XXX: The following two paragraphs do not hold for all platforms.
- // Notably, 32-bit PPC Linux passes structs by reference, not by
- // value, so this actually doesn't work. (Although that's the only
- // platform that doesn't work on.) DO NOT ATTEMPT IT. The
- // unfortunate consequence of that is that the slots which we
- // *know* are already type safe will wind up calling the type
- // checker function (when we had the slots accepting
- // BorrowedGreenlet, this was bypassed), so this slows us down.
- // TODO: Optimize this again.
-
- // For a class with a single pointer member, whose constructor
- // does nothing but copy a pointer parameter into the member, and
- // which can then be converted back to the pointer type, compilers
- // generate code that's the same as just passing the pointer.
- // That is, func(BorrowedObject x) called like ``PyObject* p =
- // ...; f(p)`` has 0 overhead. Similarly, they "unpack" to the
- // pointer type with 0 overhead.
- //
- // If there are no virtual functions, no complex inheritance (maybe?) and
- // no destructor, these can be directly used as parameters in
- // Python callbacks like tp_init: the layout is the same as a
- // single pointer. Only subclasses with trivial constructors that
- // do nothing but set the single pointer member are safe to use
- // that way.
-
-
- // This is the base class for things that can be done with a
- // PyObject pointer. It assumes nothing about memory management.
- // NOTE: Nothing is virtual, so subclasses shouldn't add new
- // storage fields or try to override these methods.
- template
- class PyObjectPointer
- {
- public:
- typedef T PyType;
- protected:
- T* p;
- public:
- PyObjectPointer(T* it=nullptr) : p(it)
- {
- TC(p);
- }
-
- // We don't allow automatic casting to PyObject* at this
- // level, because then we could be passed to Py_DECREF/INCREF,
- // but we want nothing to do with memory management. If you
- // know better, then you can use the get() method, like on a
- // std::shared_ptr. Except we name it borrow() to clarify that
- // if this is a reference-tracked object, the pointer you get
- // back will go away when the object does.
- // TODO: This should probably not exist here, but be moved
- // down to relevant sub-types.
-
- T* borrow() const noexcept
- {
- return this->p;
- }
-
- PyObject* borrow_o() const noexcept
- {
- return reinterpret_cast(this->p);
- }
-
- T* operator->() const noexcept
- {
- return this->p;
- }
-
- bool is_None() const noexcept
- {
- return this->p == Py_None;
- }
-
- PyObject* acquire_or_None() const noexcept
- {
- PyObject* result = this->p ? reinterpret_cast(this->p) : Py_None;
- Py_INCREF(result);
- return result;
- }
-
- explicit operator bool() const noexcept
- {
- return this->p != nullptr;
- }
-
- bool operator!() const noexcept
- {
- return this->p == nullptr;
- }
-
- Py_ssize_t REFCNT() const noexcept
- {
- return p ? Py_REFCNT(p) : -42;
- }
-
- PyTypeObject* TYPE() const noexcept
- {
- return p ? Py_TYPE(p) : nullptr;
- }
-
- inline OwnedObject PyStr() const noexcept;
- inline const std::string as_str() const noexcept;
- inline OwnedObject PyGetAttr(const ImmortalObject& name) const noexcept;
- inline OwnedObject PyRequireAttr(const char* const name) const;
- inline OwnedObject PyRequireAttr(const ImmortalString& name) const;
- inline OwnedObject PyCall(const BorrowedObject& arg) const;
- inline OwnedObject PyCall(PyGreenlet* arg) const ;
- inline OwnedObject PyCall(PyObject* arg) const ;
- // PyObject_Call(this, args, kwargs);
- inline OwnedObject PyCall(const BorrowedObject args,
- const BorrowedObject kwargs) const;
- inline OwnedObject PyCall(const OwnedObject& args,
- const OwnedObject& kwargs) const;
-
- protected:
- void _set_raw_pointer(void* t)
- {
- TC(t);
- p = reinterpret_cast(t);
- }
- void* _get_raw_pointer() const
- {
- return p;
- }
- };
-
-#ifdef GREENLET_USE_STDIO
- template
- std::ostream& operator<<(std::ostream& os, const PyObjectPointer& s)
- {
- const std::type_info& t = typeid(s);
- os << t.name()
- << "(addr=" << s.borrow()
- << ", refcnt=" << s.REFCNT()
- << ", value=" << s.as_str()
- << ")";
-
- return os;
- }
-#endif
-
- template
- inline bool operator==(const PyObjectPointer& lhs, const PyObject* const rhs) noexcept
- {
- return static_cast(lhs.borrow_o()) == static_cast(rhs);
- }
-
- template
- inline bool operator==(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept
- {
- return lhs.borrow_o() == rhs.borrow_o();
- }
-
- template
- inline bool operator!=(const PyObjectPointer& lhs,
- const PyObjectPointer& rhs) noexcept
- {
- return lhs.borrow_o() != rhs.borrow_o();
- }
-
- template
- class OwnedReference : public PyObjectPointer
- {
- private:
- friend class OwnedList;
-
- protected:
- explicit OwnedReference(T* it) : PyObjectPointer(it)
- {
- }
-
- public:
-
- // Constructors
-
- static OwnedReference consuming(PyObject* p)
- {
- return OwnedReference(reinterpret_cast(p));
- }
-
- static OwnedReference owning(T* p)
- {
- OwnedReference result(p);
- Py_XINCREF(result.p);
- return result;
- }
-
- OwnedReference() : PyObjectPointer(nullptr)
- {}
-
- explicit OwnedReference(const PyObjectPointer<>& other)
- : PyObjectPointer(nullptr)
- {
- T* op = other.borrow();
- TC(op);
- this->p = other.borrow();
- Py_XINCREF(this->p);
- }
-
- // It would be good to make use of the C++11 distinction
- // between move and copy operations, e.g., constructing from a
- // pointer should be a move operation.
- // In the common case of ``OwnedObject x = Py_SomeFunction()``,
- // the call to the copy constructor will be elided completely.
- OwnedReference(const OwnedReference& other)
- : PyObjectPointer(other.p)
- {
- Py_XINCREF(this->p);
- }
-
- static OwnedReference None()
- {
- Py_INCREF(Py_None);
- return OwnedReference(Py_None);
- }
-
- // We can assign from exactly our type without any extra checking
- OwnedReference& operator=(const OwnedReference& other)
- {
- Py_XINCREF(other.p);
- const T* tmp = this->p;
- this->p = other.p;
- Py_XDECREF(tmp);
- return *this;
- }
-
- OwnedReference& operator=(const BorrowedReference other)
- {
- return this->operator=(other.borrow());
- }
-
- OwnedReference& operator=(T* const other)
- {
- TC(other);
- Py_XINCREF(other);
- T* tmp = this->p;
- this->p = other;
- Py_XDECREF(tmp);
- return *this;
- }
-
- // We can assign from an arbitrary reference type
- // if it passes our check.
- template