| @ -0,0 +1,70 @@ | |||
| from flask import Blueprint, render_template, redirect, url_for, request, flash | |||
| from werkzeug.security import generate_password_hash, check_password_hash | |||
| from flask_login import login_user, logout_user, login_required | |||
| from .models import User | |||
| from . import db | |||
| bp = Blueprint('auth', __name__) | |||
| @bp.route('/login') | |||
| def login(): | |||
| return render_template('autorizacion/login.html') | |||
| @bp.route('/login', methods=['POST']) | |||
| def login_post(): | |||
| email = request.form.get('email') | |||
| password = request.form.get('password') | |||
| remember = True if request.form.get('remember') else False | |||
| user = User.query.filter_by(email=email).first() | |||
| # check if the user actually exists | |||
| # take the user-supplied password, hash it, and compare it to the hashed password in the database | |||
| if not user or not check_password_hash(user.password, password): | |||
| flash('Please check your login details and try again.') | |||
| return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page | |||
| # if the above check passes, then we know the user has the right credentials | |||
| login_user(user, remember=remember) | |||
| return redirect(url_for('reservas.misreservas')) | |||
| @bp.route('/signup') | |||
| def signup(): | |||
| return render_template('autorizacion/signup.html') | |||
| @bp.route('/signup', methods=['POST']) | |||
| def signup_post(): | |||
| email = request.form.get('email') | |||
| name = request.form.get('name') | |||
| password = request.form.get('password') | |||
| user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database | |||
| if user: # if a user is found, we want to redirect back to signup page so user can try again | |||
| flash('La dirección de correo ya existe') | |||
| return redirect(url_for('auth.signup')) | |||
| # create a new user with the form data. Hash the password so the plaintext version isn't saved. | |||
| new_user = User(email=email, name=name, password=generate_password_hash(password, method='pbkdf2:sha256')) | |||
| # add the new user to the database | |||
| db.session.add(new_user) | |||
| db.session.commit() | |||
| return redirect(url_for('auth.login')) | |||
| @bp.route('/logout') | |||
| @login_required | |||
| def logout(): | |||
| logout_user() | |||
| return redirect(url_for('auth.index')) | |||
| @bp.route('/index') | |||
| def index(): | |||
| return render_template('autorizacion/index.html') | |||
| @bp.route('/profile') | |||
| def profile(): | |||
| return render_template('autorizacion/profile.html') | |||
| @ -0,0 +1,9 @@ | |||
| from flask_login import UserMixin | |||
| from . import db | |||
| class User(UserMixin, db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | |||
| email = db.Column(db.String(100), unique=True) | |||
| password = db.Column(db.String(100)) | |||
| name = db.Column(db.String(1000)) | |||
| @ -1,8 +1,22 @@ | |||
| <nav> | |||
| <ul> | |||
| <li><a href="{{ url_for('paginas.inicio') }}">Inicio</a></li> | |||
| <li><a href="{{ url_for('paginas.acerca') }}">Acerca de...</a></li> | |||
| <li><a href="{{ url_for('reservas.misreservas') }}">Mis reservas</a></li> | |||
| <li><a href="{{ url_for('reservas.eligedia') }}">Reservar</a></li> | |||
| </ul> | |||
| </nav> | |||
| <div class="hero-head"> | |||
| <nav class="navbar"> | |||
| <div class="container"> | |||
| <div id="navbarMenuHeroA" class="navbar-menu"> | |||
| <div class="navbar-end"> | |||
| <a href="{{ url_for('paginas.inicio') }}" class="navbar-item"> | |||
| Inicio | |||
| </a> | |||
| <a href="{{ url_for('paginas.acerca') }}" class="navbar-item"> | |||
| Acerca de... | |||
| </a> | |||
| <a href="{{ url_for('reservas.misreservas') }}" class="navbar-item"> | |||
| Mis reservas | |||
| </a> | |||
| <a href="{{ url_for('reservas.eligedia') }}" class="navbar-item"> | |||
| Reservar | |||
| </a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </nav> | |||
| </div> | |||
| @ -0,0 +1,10 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <h1 class="title"> | |||
| Flask Login Example | |||
| </h1> | |||
| <h2 class="subtitle"> | |||
| Easy authentication and authorization in Flask. | |||
| </h2> | |||
| {% endblock %} | |||
| @ -0,0 +1,36 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3 class="title">Login</h3> | |||
| <div class="box"> | |||
| {% with messages = get_flashed_messages() %} | |||
| {% if messages %} | |||
| <div class="notification is-danger"> | |||
| {{ messages[0] }} | |||
| </div> | |||
| {% endif %} | |||
| {% endwith %} | |||
| <form method="POST" action="/login"> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="password" name="password" placeholder="Your Password"> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="checkbox"> | |||
| <input type="checkbox"> | |||
| Remember me | |||
| </label> | |||
| </div> | |||
| <button class="button is-block is-info is-large is-fullwidth">Login</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||
| @ -0,0 +1,7 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <h1 class="title"> | |||
| Welcome, Anthony! | |||
| </h1> | |||
| {% endblock %} | |||
| @ -0,0 +1,39 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3 class="title">Sign Up</h3> | |||
| <div class="box"> | |||
| {% with messages = get_flashed_messages() %} | |||
| {% if messages %} | |||
| <div class="notification is-danger"> | |||
| {{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>. | |||
| </div> | |||
| {% endif %} | |||
| {% endwith %} | |||
| <form method="POST" action="/signup"> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="email" name="email" placeholder="Email" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="text" name="name" placeholder="Name" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="password" name="password" placeholder="Password"> | |||
| </div> | |||
| </div> | |||
| <button class="button is-block is-info is-large is-fullwidth">Sign Up</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||
| @ -0,0 +1,247 @@ | |||
| <# | |||
| .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" | |||
| @ -0,0 +1,69 @@ | |||
| # 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 | |||
| # This should detect bash and zsh, which have a hash command that must | |||
| # be called to get it to forget past commands. Without forgetting | |||
| # past commands the $PATH changes we made may not be respected | |||
| if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then | |||
| hash -r 2> /dev/null | |||
| fi | |||
| 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 | |||
| VIRTUAL_ENV="/home/creylopez/AppsPy/flask_auth_app/authenv" | |||
| export VIRTUAL_ENV | |||
| _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="(authenv) ${PS1:-}" | |||
| export PS1 | |||
| VIRTUAL_ENV_PROMPT="(authenv) " | |||
| export VIRTUAL_ENV_PROMPT | |||
| fi | |||
| # This should detect bash and zsh, which have a hash command that must | |||
| # be called to get it to forget past commands. Without forgetting | |||
| # past commands the $PATH changes we made may not be respected | |||
| if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then | |||
| hash -r 2> /dev/null | |||
| fi | |||
| @ -0,0 +1,26 @@ | |||
| # This file must be used with "source bin/activate.csh" *from csh*. | |||
| # You cannot run it directly. | |||
| # Created by Davide Di Blasi <davidedb@gmail.com>. | |||
| # Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com> | |||
| 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/creylopez/AppsPy/flask_auth_app/authenv" | |||
| set _OLD_VIRTUAL_PATH="$PATH" | |||
| setenv PATH "$VIRTUAL_ENV/bin:$PATH" | |||
| set _OLD_VIRTUAL_PROMPT="$prompt" | |||
| if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then | |||
| set prompt = "(authenv) $prompt" | |||
| setenv VIRTUAL_ENV_PROMPT "(authenv) " | |||
| endif | |||
| alias pydoc python -m pydoc | |||
| rehash | |||
| @ -0,0 +1,69 @@ | |||
| # This file must be used with "source <venv>/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/creylopez/AppsPy/flask_auth_app/authenv" | |||
| 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) "(authenv) " (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 "(authenv) " | |||
| end | |||
| @ -0,0 +1,8 @@ | |||
| #!/home/creylopez/AppsPy/flask_auth_app/authenv/bin/python3 | |||
| # -*- coding: utf-8 -*- | |||
| import re | |||
| import sys | |||
| from dotenv.__main__ import cli | |||
| if __name__ == '__main__': | |||
| sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | |||
| sys.exit(cli()) | |||
| @ -0,0 +1,8 @@ | |||
| #!/home/creylopez/AppsPy/flask_auth_app/authenv/bin/python3 | |||
| # -*- coding: utf-8 -*- | |||
| import re | |||
| import sys | |||
| from flask.cli import main | |||
| if __name__ == '__main__': | |||
| sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | |||
| sys.exit(main()) | |||
| @ -0,0 +1,8 @@ | |||
| #!/home/creylopez/AppsPy/flask_auth_app/authenv/bin/python3 | |||
| # -*- 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()) | |||
| @ -0,0 +1,8 @@ | |||
| #!/home/creylopez/AppsPy/flask_auth_app/authenv/bin/python3 | |||
| # -*- 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()) | |||
| @ -0,0 +1,8 @@ | |||
| #!/home/creylopez/AppsPy/flask_auth_app/authenv/bin/python3 | |||
| # -*- 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()) | |||
| @ -0,0 +1 @@ | |||
| python3 | |||
| @ -0,0 +1 @@ | |||
| /usr/bin/python3 | |||
| @ -0,0 +1 @@ | |||
| python3 | |||
| @ -0,0 +1,164 @@ | |||
| /* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ | |||
| /* Greenlet object interface */ | |||
| #ifndef Py_GREENLETOBJECT_H | |||
| #define Py_GREENLETOBJECT_H | |||
| #include <Python.h> | |||
| #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 */ | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,22 @@ | |||
| Copyright (c) 2011 Matthew Frazier | |||
| 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. | |||
| @ -0,0 +1,183 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: Flask-Login | |||
| Version: 0.6.3 | |||
| Summary: User authentication and session management for Flask. | |||
| Home-page: https://github.com/maxcountryman/flask-login | |||
| Author: Matthew Frazier | |||
| Author-email: leafstormrush@gmail.com | |||
| Maintainer: Max Countryman | |||
| License: MIT | |||
| Project-URL: Documentation, https://flask-login.readthedocs.io/ | |||
| Project-URL: Changes, https://github.com/maxcountryman/flask-login/blob/main/CHANGES.md | |||
| Project-URL: Source Code, https://github.com/maxcountryman/flask-login | |||
| Project-URL: Issue Tracker, https://github.com/maxcountryman/flask-login/issues | |||
| Classifier: Development Status :: 4 - Beta | |||
| Classifier: Environment :: Web Environment | |||
| Classifier: Framework :: Flask | |||
| Classifier: Intended Audience :: Developers | |||
| Classifier: License :: OSI Approved :: MIT License | |||
| Classifier: Operating System :: OS Independent | |||
| Classifier: Programming Language :: Python | |||
| Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content | |||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | |||
| Requires-Python: >=3.7 | |||
| Description-Content-Type: text/markdown | |||
| License-File: LICENSE | |||
| Requires-Dist: Flask >=1.0.4 | |||
| Requires-Dist: Werkzeug >=1.0.1 | |||
| # Flask-Login | |||
|  | |||
| [](https://coveralls.io/github/maxcountryman/flask-login?branch=main) | |||
| [](LICENSE) | |||
| Flask-Login provides user session management for Flask. It handles the common | |||
| tasks of logging in, logging out, and remembering your users' sessions over | |||
| extended periods of time. | |||
| Flask-Login is not bound to any particular database system or permissions | |||
| model. The only requirement is that your user objects implement a few methods, | |||
| and that you provide a callback to the extension capable of loading users from | |||
| their ID. | |||
| ## Installation | |||
| Install the extension with pip: | |||
| ```sh | |||
| $ pip install flask-login | |||
| ``` | |||
| ## Usage | |||
| Once installed, the Flask-Login is easy to use. Let's walk through setting up | |||
| a basic application. Also please note that this is a very basic guide: we will | |||
| be taking shortcuts here that you should never take in a real application. | |||
| To begin we'll set up a Flask app: | |||
| ```python | |||
| import flask | |||
| app = flask.Flask(__name__) | |||
| app.secret_key = 'super secret string' # Change this! | |||
| ``` | |||
| Flask-Login works via a login manager. To kick things off, we'll set up the | |||
| login manager by instantiating it and telling it about our Flask app: | |||
| ```python | |||
| import flask_login | |||
| login_manager = flask_login.LoginManager() | |||
| login_manager.init_app(app) | |||
| ``` | |||
| To keep things simple we're going to use a dictionary to represent a database | |||
| of users. In a real application, this would be an actual persistence layer. | |||
| However it's important to point out this is a feature of Flask-Login: it | |||
| doesn't care how your data is stored so long as you tell it how to retrieve it! | |||
| ```python | |||
| # Our mock database. | |||
| users = {'foo@bar.tld': {'password': 'secret'}} | |||
| ``` | |||
| We also need to tell Flask-Login how to load a user from a Flask request and | |||
| from its session. To do this we need to define our user object, a | |||
| `user_loader` callback, and a `request_loader` callback. | |||
| ```python | |||
| class User(flask_login.UserMixin): | |||
| pass | |||
| @login_manager.user_loader | |||
| def user_loader(email): | |||
| if email not in users: | |||
| return | |||
| user = User() | |||
| user.id = email | |||
| return user | |||
| @login_manager.request_loader | |||
| def request_loader(request): | |||
| email = request.form.get('email') | |||
| if email not in users: | |||
| return | |||
| user = User() | |||
| user.id = email | |||
| return user | |||
| ``` | |||
| Now we're ready to define our views. We can start with a login view, which will | |||
| populate the session with authentication bits. After that we can define a view | |||
| that requires authentication. | |||
| ```python | |||
| @app.route('/login', methods=['GET', 'POST']) | |||
| def login(): | |||
| if flask.request.method == 'GET': | |||
| return ''' | |||
| <form action='login' method='POST'> | |||
| <input type='text' name='email' id='email' placeholder='email'/> | |||
| <input type='password' name='password' id='password' placeholder='password'/> | |||
| <input type='submit' name='submit'/> | |||
| </form> | |||
| ''' | |||
| email = flask.request.form['email'] | |||
| if email in users and flask.request.form['password'] == users[email]['password']: | |||
| user = User() | |||
| user.id = email | |||
| flask_login.login_user(user) | |||
| return flask.redirect(flask.url_for('protected')) | |||
| return 'Bad login' | |||
| @app.route('/protected') | |||
| @flask_login.login_required | |||
| def protected(): | |||
| return 'Logged in as: ' + flask_login.current_user.id | |||
| ``` | |||
| Finally we can define a view to clear the session and log users out: | |||
| ```python | |||
| @app.route('/logout') | |||
| def logout(): | |||
| flask_login.logout_user() | |||
| return 'Logged out' | |||
| ``` | |||
| We now have a basic working application that makes use of session-based | |||
| authentication. To round things off, we should provide a callback for login | |||
| failures: | |||
| ```python | |||
| @login_manager.unauthorized_handler | |||
| def unauthorized_handler(): | |||
| return 'Unauthorized', 401 | |||
| ``` | |||
| Documentation for Flask-Login is available on [ReadTheDocs](https://flask-login.readthedocs.io/en/latest/). | |||
| For complete understanding of available configuration, please refer to the [source code](https://github.com/maxcountryman/flask-login). | |||
| ## Contributing | |||
| We welcome contributions! If you would like to hack on Flask-Login, please | |||
| follow these steps: | |||
| 1. Fork this repository | |||
| 2. Make your changes | |||
| 3. Install the dev requirements with `pip install -r requirements/dev.txt` | |||
| 4. Submit a pull request after running `tox` (ensure it does not error!) | |||
| Please give us adequate time to review your submission. Thanks! | |||
| @ -0,0 +1,23 @@ | |||
| Flask_Login-0.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| Flask_Login-0.6.3.dist-info/LICENSE,sha256=ep37nF2iBO0TcPO2LBPimSoS2h2nB_R-FWiX7rQ0Tls,1059 | |||
| Flask_Login-0.6.3.dist-info/METADATA,sha256=AUSHR5Po6-Cwmz1KBrAZbTzR-iVVFvtb2NQKYl7UuAU,5799 | |||
| Flask_Login-0.6.3.dist-info/RECORD,, | |||
| Flask_Login-0.6.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| Flask_Login-0.6.3.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92 | |||
| Flask_Login-0.6.3.dist-info/top_level.txt,sha256=OuXmIpiFnXLvW-iBbW2km7ZIy5EZvwSBnYaOC3Kt7j8,12 | |||
| flask_login/__about__.py,sha256=Kkp5e9mV9G7vK_FqZof-g9RFmyyBzq1gge5aKXgvilE,389 | |||
| flask_login/__init__.py,sha256=wYQiQCikT_Ndp3PhOD-1gRTGCrUPIE-FrjQUrT9aVAg,2681 | |||
| flask_login/__pycache__/__about__.cpython-310.pyc,, | |||
| flask_login/__pycache__/__init__.cpython-310.pyc,, | |||
| flask_login/__pycache__/config.cpython-310.pyc,, | |||
| flask_login/__pycache__/login_manager.cpython-310.pyc,, | |||
| flask_login/__pycache__/mixins.cpython-310.pyc,, | |||
| flask_login/__pycache__/signals.cpython-310.pyc,, | |||
| flask_login/__pycache__/test_client.cpython-310.pyc,, | |||
| flask_login/__pycache__/utils.cpython-310.pyc,, | |||
| flask_login/config.py,sha256=YAocv18La7YGQyNY5aT7rU1GQIZnX6pvchwqx3kA9p8,1813 | |||
| flask_login/login_manager.py,sha256=h20F_iv3mqc6rIJ4-V6_XookzOUl8Rcpasua-dCByQY,20073 | |||
| flask_login/mixins.py,sha256=gPd7otMRljxw0eUhUMbHsnEBc_jK2cYdxg5KFLuJcoI,1528 | |||
| flask_login/signals.py,sha256=xCMoFHKU1RTVt1NY-Gfl0OiVKpiyNt6YJw_PsgkjY3w,2464 | |||
| flask_login/test_client.py,sha256=6mrjiBRLGJpgvvFlLypXPTBLiMp0BAN-Ft-uogqC81g,517 | |||
| flask_login/utils.py,sha256=Y1wxjCVxpYohBaQJ0ADLypQ-VvBNycwG-gVXFF7k99I,14021 | |||
| @ -0,0 +1,5 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: bdist_wheel (0.41.3) | |||
| Root-Is-Purelib: true | |||
| Tag: py3-none-any | |||
| @ -0,0 +1 @@ | |||
| flask_login | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,28 @@ | |||
| Copyright 2010 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. | |||
| @ -0,0 +1,93 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: MarkupSafe | |||
| Version: 2.1.5 | |||
| Summary: Safely add untrusted strings to HTML/XML markup. | |||
| Home-page: https://palletsprojects.com/p/markupsafe/ | |||
| Maintainer: Pallets | |||
| Maintainer-email: contact@palletsprojects.com | |||
| License: BSD-3-Clause | |||
| Project-URL: Donate, https://palletsprojects.com/donate | |||
| Project-URL: Documentation, https://markupsafe.palletsprojects.com/ | |||
| Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ | |||
| Project-URL: Source Code, https://github.com/pallets/markupsafe/ | |||
| Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/ | |||
| Project-URL: Chat, https://discord.gg/pallets | |||
| Classifier: Development Status :: 5 - Production/Stable | |||
| Classifier: Environment :: Web Environment | |||
| Classifier: Intended Audience :: Developers | |||
| Classifier: License :: OSI Approved :: BSD License | |||
| Classifier: Operating System :: OS Independent | |||
| Classifier: Programming Language :: Python | |||
| Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content | |||
| Classifier: Topic :: Text Processing :: Markup :: HTML | |||
| Requires-Python: >=3.7 | |||
| Description-Content-Type: text/x-rst | |||
| License-File: LICENSE.rst | |||
| MarkupSafe | |||
| ========== | |||
| MarkupSafe implements a text object that escapes characters so it is | |||
| safe to use in HTML and XML. Characters that have special meanings are | |||
| replaced so that they display as the actual characters. This mitigates | |||
| injection attacks, meaning untrusted user input can safely be displayed | |||
| on a page. | |||
| Installing | |||
| ---------- | |||
| Install and update using `pip`_: | |||
| .. code-block:: text | |||
| pip install -U MarkupSafe | |||
| .. _pip: https://pip.pypa.io/en/stable/getting-started/ | |||
| Examples | |||
| -------- | |||
| .. code-block:: pycon | |||
| >>> from markupsafe import Markup, escape | |||
| >>> # escape replaces special characters and wraps in Markup | |||
| >>> escape("<script>alert(document.cookie);</script>") | |||
| Markup('<script>alert(document.cookie);</script>') | |||
| >>> # wrap in Markup to mark text "safe" and prevent escaping | |||
| >>> Markup("<strong>Hello</strong>") | |||
| Markup('<strong>hello</strong>') | |||
| >>> escape(Markup("<strong>Hello</strong>")) | |||
| Markup('<strong>hello</strong>') | |||
| >>> # Markup is a str subclass | |||
| >>> # methods and operators escape their arguments | |||
| >>> template = Markup("Hello <em>{name}</em>") | |||
| >>> template.format(name='"World"') | |||
| Markup('Hello <em>"World"</em>') | |||
| Donate | |||
| ------ | |||
| The Pallets organization develops and supports MarkupSafe 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 | |||
| Links | |||
| ----- | |||
| - Documentation: https://markupsafe.palletsprojects.com/ | |||
| - Changes: https://markupsafe.palletsprojects.com/changes/ | |||
| - PyPI Releases: https://pypi.org/project/MarkupSafe/ | |||
| - Source Code: https://github.com/pallets/markupsafe/ | |||
| - Issue Tracker: https://github.com/pallets/markupsafe/issues/ | |||
| - Chat: https://discord.gg/pallets | |||
| @ -0,0 +1,14 @@ | |||
| MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 | |||
| MarkupSafe-2.1.5.dist-info/METADATA,sha256=2dRDPam6OZLfpX0wg1JN5P3u9arqACxVSfdGmsJU7o8,3003 | |||
| MarkupSafe-2.1.5.dist-info/RECORD,, | |||
| MarkupSafe-2.1.5.dist-info/WHEEL,sha256=1FEjxEYgybphwh9S0FO9IcZ0B-NIeM2ko8OzhFZeOeQ,152 | |||
| MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 | |||
| markupsafe/__init__.py,sha256=r7VOTjUq7EMQ4v3p4R1LoVOGJg6ysfYRncLr34laRBs,10958 | |||
| markupsafe/__pycache__/__init__.cpython-310.pyc,, | |||
| markupsafe/__pycache__/_native.cpython-310.pyc,, | |||
| markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713 | |||
| markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083 | |||
| markupsafe/_speedups.cpython-310-x86_64-linux-gnu.so,sha256=kPt-fhZ_RG7PUbDvwmyC26ZvRJ9DvUlF3hszBIB6_xs,44240 | |||
| markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229 | |||
| markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| @ -0,0 +1,6 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: bdist_wheel (0.42.0) | |||
| Root-Is-Purelib: false | |||
| Tag: cp310-cp310-manylinux_2_17_x86_64 | |||
| Tag: cp310-cp310-manylinux2014_x86_64 | |||
| @ -0,0 +1 @@ | |||
| markupsafe | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,19 @@ | |||
| Copyright 2005-2024 SQLAlchemy authors and contributors <see AUTHORS file>. | |||
| 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. | |||
| @ -0,0 +1,242 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: SQLAlchemy | |||
| Version: 2.0.30 | |||
| Summary: Database Abstraction Library | |||
| Home-page: https://www.sqlalchemy.org | |||
| Author: Mike Bayer | |||
| Author-email: mike_mp@zzzcomputing.com | |||
| License: MIT | |||
| Project-URL: Documentation, https://docs.sqlalchemy.org | |||
| Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/ | |||
| Classifier: Development Status :: 5 - Production/Stable | |||
| Classifier: Intended Audience :: Developers | |||
| Classifier: License :: OSI Approved :: MIT License | |||
| Classifier: Operating System :: OS Independent | |||
| Classifier: Programming Language :: Python | |||
| Classifier: Programming Language :: Python :: 3 | |||
| Classifier: Programming Language :: Python :: 3.7 | |||
| 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 :: Implementation :: CPython | |||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | |||
| Classifier: Topic :: Database :: Front-Ends | |||
| Requires-Python: >=3.7 | |||
| Description-Content-Type: text/x-rst | |||
| License-File: LICENSE | |||
| Requires-Dist: typing-extensions >=4.6.0 | |||
| Requires-Dist: greenlet !=0.4.17 ; platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32"))))) | |||
| Requires-Dist: importlib-metadata ; python_version < "3.8" | |||
| Provides-Extra: aiomysql | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'aiomysql' | |||
| Requires-Dist: aiomysql >=0.2.0 ; extra == 'aiomysql' | |||
| Provides-Extra: aioodbc | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'aioodbc' | |||
| Requires-Dist: aioodbc ; extra == 'aioodbc' | |||
| Provides-Extra: aiosqlite | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'aiosqlite' | |||
| Requires-Dist: aiosqlite ; extra == 'aiosqlite' | |||
| Requires-Dist: typing-extensions !=3.10.0.1 ; extra == 'aiosqlite' | |||
| Provides-Extra: asyncio | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncio' | |||
| Provides-Extra: asyncmy | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncmy' | |||
| Requires-Dist: asyncmy !=0.2.4,!=0.2.6,>=0.2.3 ; extra == 'asyncmy' | |||
| Provides-Extra: mariadb_connector | |||
| Requires-Dist: mariadb !=1.1.2,!=1.1.5,>=1.0.1 ; extra == 'mariadb_connector' | |||
| Provides-Extra: mssql | |||
| Requires-Dist: pyodbc ; extra == 'mssql' | |||
| Provides-Extra: mssql_pymssql | |||
| Requires-Dist: pymssql ; extra == 'mssql_pymssql' | |||
| Provides-Extra: mssql_pyodbc | |||
| Requires-Dist: pyodbc ; extra == 'mssql_pyodbc' | |||
| Provides-Extra: mypy | |||
| Requires-Dist: mypy >=0.910 ; extra == 'mypy' | |||
| Provides-Extra: mysql | |||
| Requires-Dist: mysqlclient >=1.4.0 ; extra == 'mysql' | |||
| Provides-Extra: mysql_connector | |||
| Requires-Dist: mysql-connector-python ; extra == 'mysql_connector' | |||
| Provides-Extra: oracle | |||
| Requires-Dist: cx-oracle >=8 ; extra == 'oracle' | |||
| Provides-Extra: oracle_oracledb | |||
| Requires-Dist: oracledb >=1.0.1 ; extra == 'oracle_oracledb' | |||
| Provides-Extra: postgresql | |||
| Requires-Dist: psycopg2 >=2.7 ; extra == 'postgresql' | |||
| Provides-Extra: postgresql_asyncpg | |||
| Requires-Dist: greenlet !=0.4.17 ; extra == 'postgresql_asyncpg' | |||
| Requires-Dist: asyncpg ; extra == 'postgresql_asyncpg' | |||
| Provides-Extra: postgresql_pg8000 | |||
| Requires-Dist: pg8000 >=1.29.1 ; extra == 'postgresql_pg8000' | |||
| Provides-Extra: postgresql_psycopg | |||
| Requires-Dist: psycopg >=3.0.7 ; extra == 'postgresql_psycopg' | |||
| Provides-Extra: postgresql_psycopg2binary | |||
| Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary' | |||
| Provides-Extra: postgresql_psycopg2cffi | |||
| Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi' | |||
| Provides-Extra: postgresql_psycopgbinary | |||
| Requires-Dist: psycopg[binary] >=3.0.7 ; extra == 'postgresql_psycopgbinary' | |||
| Provides-Extra: pymysql | |||
| Requires-Dist: pymysql ; extra == 'pymysql' | |||
| Provides-Extra: sqlcipher | |||
| Requires-Dist: sqlcipher3-binary ; extra == 'sqlcipher' | |||
| SQLAlchemy | |||
| ========== | |||
| |PyPI| |Python| |Downloads| | |||
| .. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy | |||
| :target: https://pypi.org/project/sqlalchemy | |||
| :alt: PyPI | |||
| .. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy | |||
| :target: https://pypi.org/project/sqlalchemy | |||
| :alt: PyPI - Python Version | |||
| .. |Downloads| image:: https://static.pepy.tech/badge/sqlalchemy/month | |||
| :target: https://pepy.tech/project/sqlalchemy | |||
| :alt: PyPI - Downloads | |||
| The Python SQL Toolkit and Object Relational Mapper | |||
| Introduction | |||
| ------------- | |||
| SQLAlchemy is the Python SQL toolkit and Object Relational Mapper | |||
| that gives application developers the full power and | |||
| flexibility of SQL. SQLAlchemy provides a full suite | |||
| of well known enterprise-level persistence patterns, | |||
| designed for efficient and high-performing database | |||
| access, adapted into a simple and Pythonic domain | |||
| language. | |||
| Major SQLAlchemy features include: | |||
| * An industrial strength ORM, built | |||
| from the core on the identity map, unit of work, | |||
| and data mapper patterns. These patterns | |||
| allow transparent persistence of objects | |||
| using a declarative configuration system. | |||
| Domain models | |||
| can be constructed and manipulated naturally, | |||
| and changes are synchronized with the | |||
| current transaction automatically. | |||
| * A relationally-oriented query system, exposing | |||
| the full range of SQL's capabilities | |||
| explicitly, including joins, subqueries, | |||
| correlation, and most everything else, | |||
| in terms of the object model. | |||
| Writing queries with the ORM uses the same | |||
| techniques of relational composition you use | |||
| when writing SQL. While you can drop into | |||
| literal SQL at any time, it's virtually never | |||
| needed. | |||
| * A comprehensive and flexible system | |||
| of eager loading for related collections and objects. | |||
| Collections are cached within a session, | |||
| and can be loaded on individual access, all | |||
| at once using joins, or by query per collection | |||
| across the full result set. | |||
| * A Core SQL construction system and DBAPI | |||
| interaction layer. The SQLAlchemy Core is | |||
| separate from the ORM and is a full database | |||
| abstraction layer in its own right, and includes | |||
| an extensible Python-based SQL expression | |||
| language, schema metadata, connection pooling, | |||
| type coercion, and custom types. | |||
| * All primary and foreign key constraints are | |||
| assumed to be composite and natural. Surrogate | |||
| integer primary keys are of course still the | |||
| norm, but SQLAlchemy never assumes or hardcodes | |||
| to this model. | |||
| * Database introspection and generation. Database | |||
| schemas can be "reflected" in one step into | |||
| Python structures representing database metadata; | |||
| those same structures can then generate | |||
| CREATE statements right back out - all within | |||
| the Core, independent of the ORM. | |||
| SQLAlchemy's philosophy: | |||
| * SQL databases behave less and less like object | |||
| collections the more size and performance start to | |||
| matter; object collections behave less and less like | |||
| tables and rows the more abstraction starts to matter. | |||
| SQLAlchemy aims to accommodate both of these | |||
| principles. | |||
| * An ORM doesn't need to hide the "R". A relational | |||
| database provides rich, set-based functionality | |||
| that should be fully exposed. SQLAlchemy's | |||
| ORM provides an open-ended set of patterns | |||
| that allow a developer to construct a custom | |||
| mediation layer between a domain model and | |||
| a relational schema, turning the so-called | |||
| "object relational impedance" issue into | |||
| a distant memory. | |||
| * The developer, in all cases, makes all decisions | |||
| regarding the design, structure, and naming conventions | |||
| of both the object model as well as the relational | |||
| schema. SQLAlchemy only provides the means | |||
| to automate the execution of these decisions. | |||
| * With SQLAlchemy, there's no such thing as | |||
| "the ORM generated a bad query" - you | |||
| retain full control over the structure of | |||
| queries, including how joins are organized, | |||
| how subqueries and correlation is used, what | |||
| columns are requested. Everything SQLAlchemy | |||
| does is ultimately the result of a developer-initiated | |||
| decision. | |||
| * Don't use an ORM if the problem doesn't need one. | |||
| SQLAlchemy consists of a Core and separate ORM | |||
| component. The Core offers a full SQL expression | |||
| language that allows Pythonic construction | |||
| of SQL constructs that render directly to SQL | |||
| strings for a target database, returning | |||
| result sets that are essentially enhanced DBAPI | |||
| cursors. | |||
| * Transactions should be the norm. With SQLAlchemy's | |||
| ORM, nothing goes to permanent storage until | |||
| commit() is called. SQLAlchemy encourages applications | |||
| to create a consistent means of delineating | |||
| the start and end of a series of operations. | |||
| * Never render a literal value in a SQL statement. | |||
| Bound parameters are used to the greatest degree | |||
| possible, allowing query optimizers to cache | |||
| query plans effectively and making SQL injection | |||
| attacks a non-issue. | |||
| Documentation | |||
| ------------- | |||
| Latest documentation is at: | |||
| https://www.sqlalchemy.org/docs/ | |||
| Installation / Requirements | |||
| --------------------------- | |||
| Full documentation for installation is at | |||
| `Installation <https://www.sqlalchemy.org/docs/intro.html#installation>`_. | |||
| Getting Help / Development / Bug reporting | |||
| ------------------------------------------ | |||
| Please refer to the `SQLAlchemy Community Guide <https://www.sqlalchemy.org/support.html>`_. | |||
| 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 <https://www.sqlalchemy.org/codeofconduct.html>`_. | |||
| License | |||
| ------- | |||
| SQLAlchemy is distributed under the `MIT license | |||
| <https://www.opensource.org/licenses/mit-license.php>`_. | |||
| @ -0,0 +1,529 @@ | |||
| SQLAlchemy-2.0.30.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| SQLAlchemy-2.0.30.dist-info/LICENSE,sha256=PA9Zq4h9BB3mpOUv_j6e212VIt6Qn66abNettue-MpM,1100 | |||
| SQLAlchemy-2.0.30.dist-info/METADATA,sha256=je_u9qkpPX36jMg6zWrd8VjcEuly2P0zmsxfGP9GAHE,9602 | |||
| SQLAlchemy-2.0.30.dist-info/RECORD,, | |||
| SQLAlchemy-2.0.30.dist-info/WHEEL,sha256=CzQQWV-lNyM92gr3iaBk8dvO35YDHRxgzkZ-dxumUIM,152 | |||
| SQLAlchemy-2.0.30.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11 | |||
| sqlalchemy/__init__.py,sha256=yI12J_Tdp5kmBfaR8P8wQjcZg4U0qua6pbbB7JTvCsw,13033 | |||
| sqlalchemy/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/events.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/exc.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/inspection.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/log.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/schema.cpython-310.pyc,, | |||
| sqlalchemy/__pycache__/types.cpython-310.pyc,, | |||
| sqlalchemy/connectors/__init__.py,sha256=PzXPqZqi3BzEnrs1eW0DcsR4lyknAzhhN9rWcQ97hb4,476 | |||
| sqlalchemy/connectors/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/connectors/__pycache__/aioodbc.cpython-310.pyc,, | |||
| sqlalchemy/connectors/__pycache__/asyncio.cpython-310.pyc,, | |||
| sqlalchemy/connectors/__pycache__/pyodbc.cpython-310.pyc,, | |||
| sqlalchemy/connectors/aioodbc.py,sha256=GSTiNMO9h0qjPxgqaxDwWZ8HvhWMFNVR6MJQnN1oc40,5288 | |||
| sqlalchemy/connectors/asyncio.py,sha256=K3U5857TD0GmJXIhxgRljKnJhJNzYFhQ8UjyFX0Yyoo,5878 | |||
| sqlalchemy/connectors/pyodbc.py,sha256=t7AjyxIOnaWg3CrlUEpBs4Y5l0HFdNt3P_cSSKhbi0Y,8501 | |||
| sqlalchemy/cyextension/__init__.py,sha256=GzhhN8cjMnDTE0qerlUlpbrNmFPHQWCZ4Gk74OAxl04,244 | |||
| sqlalchemy/cyextension/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/cyextension/collections.cpython-310-x86_64-linux-gnu.so,sha256=8od2cbgOsahxxeSGA0tRaCAFP2tRJAKA-XROqBE0Tug,1868216 | |||
| sqlalchemy/cyextension/collections.pyx,sha256=L7DZ3DGKpgw2MT2ZZRRxCnrcyE5pU1NAFowWgAzQPEc,12571 | |||
| sqlalchemy/cyextension/immutabledict.cpython-310-x86_64-linux-gnu.so,sha256=9ikr_c1y8B09IKjt2bzjfVLUH98rv1tjY2e8TsbClvk,587592 | |||
| sqlalchemy/cyextension/immutabledict.pxd,sha256=3x3-rXG5eRQ7bBnktZ-OJ9-6ft8zToPmTDOd92iXpB0,291 | |||
| sqlalchemy/cyextension/immutabledict.pyx,sha256=KfDTYbTfebstE8xuqAtuXsHNAK0_b5q_ymUiinUe_xs,3535 | |||
| sqlalchemy/cyextension/processors.cpython-310-x86_64-linux-gnu.so,sha256=gjZUjwboTD2ARYslv_kINc248FkgLDqhrilpGE9s3o0,431544 | |||
| sqlalchemy/cyextension/processors.pyx,sha256=R1rHsGLEaGeBq5VeCydjClzYlivERIJ9B-XLOJlf2MQ,1792 | |||
| sqlalchemy/cyextension/resultproxy.cpython-310-x86_64-linux-gnu.so,sha256=pgZceU3BnCgrTQM8KZVVI3HxAUQdeZSfyJjcyx7zZwM,510168 | |||
| sqlalchemy/cyextension/resultproxy.pyx,sha256=eWLdyBXiBy_CLQrF5ScfWJm7X0NeelscSXedtj1zv9Q,2725 | |||
| sqlalchemy/cyextension/util.cpython-310-x86_64-linux-gnu.so,sha256=Ytj9EHZjmPg5Zkw1ZXO5Q5kTvOgrXp-rGb8pFzweBsg,725904 | |||
| sqlalchemy/cyextension/util.pyx,sha256=B85orxa9LddLuQEaDoVSq1XmAXIbLKxrxpvuB8ogV_o,2530 | |||
| sqlalchemy/dialects/__init__.py,sha256=Kos9Gf5JZg1Vg6GWaCqEbD6e0r1jCwCmcnJIfcxDdcY,1770 | |||
| sqlalchemy/dialects/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/__pycache__/_typing.cpython-310.pyc,, | |||
| sqlalchemy/dialects/_typing.py,sha256=hyv0nKucX2gI8ispB1IsvaUgrEPn9zEcq9hS7kfstEw,888 | |||
| sqlalchemy/dialects/mssql/__init__.py,sha256=r5t8wFRNtBQoiUWh0WfIEWzXZW6f3D0uDt6NZTW_7Cc,1880 | |||
| sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/json.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mssql/aioodbc.py,sha256=UQd9ecSMIML713TDnLAviuBVJle7P7i1FtqGZZePk2Y,2022 | |||
| sqlalchemy/dialects/mssql/base.py,sha256=87Z8bQbA8q5_T2Oyd4tUuttWNjYTQD9Dhq-eplDJcY4,132301 | |||
| sqlalchemy/dialects/mssql/information_schema.py,sha256=HswjDc6y0mPXCf_x6VyylHlBdBa4PSY6Evxmmlch700,8084 | |||
| sqlalchemy/dialects/mssql/json.py,sha256=evUACW2O62TAPq8B7QIPagz7jfc664ql9ms68JqiYzg,4816 | |||
| sqlalchemy/dialects/mssql/provision.py,sha256=RTVbgYLFAHzEnpVQDJroU8ji_10MqBTiZfyP9_-QNT4,5362 | |||
| sqlalchemy/dialects/mssql/pymssql.py,sha256=eZRLz7HGt3SdoZUjFBmA9BS43N7AhIASw7VPBPEJuG0,4038 | |||
| sqlalchemy/dialects/mssql/pyodbc.py,sha256=vwM-vBlmRwrqxOc73P0sFOrBSwn24wzc5IkEOpalbXQ,27056 | |||
| sqlalchemy/dialects/mysql/__init__.py,sha256=bxbi4hkysUK2OOVvr1F49akUj1cky27kKb07tgFzI9U,2153 | |||
| sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/dml.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/expression.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/json.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/__pycache__/types.cpython-310.pyc,, | |||
| sqlalchemy/dialects/mysql/aiomysql.py,sha256=67JrSUD1BmN88k_ASk6GvrttZFQiFjDY0wBiwdllxMk,9964 | |||
| sqlalchemy/dialects/mysql/asyncmy.py,sha256=CGILIRKf_2Ut9Ng2yBlmdg62laL-ockEm6GMuN7xlKE,10033 | |||
| sqlalchemy/dialects/mysql/base.py,sha256=KA7tvRxKUw0KwHwMth2rz-NWV0xMkVbYvPoBM9wrAFw,120850 | |||
| sqlalchemy/dialects/mysql/cymysql.py,sha256=eXT1ry0w_qRxjiO24M980c-8PZ9qSsbhqBHntjEiKB0,2300 | |||
| sqlalchemy/dialects/mysql/dml.py,sha256=HXJMAvimJsqvhj3UZO4vW_6LkF5RqaKbHvklAjor7yU,7645 | |||
| sqlalchemy/dialects/mysql/enumerated.py,sha256=ipEPPQqoXfFwcywNdcLlZCEzHBtnitHRah1Gn6nItcg,8448 | |||
| sqlalchemy/dialects/mysql/expression.py,sha256=lsmQCHKwfPezUnt27d2kR6ohk4IRFCA64KBS16kx5dc,4097 | |||
| sqlalchemy/dialects/mysql/json.py,sha256=l6MEZ0qp8FgiRrIQvOMhyEJq0q6OqiEnvDTx5Cbt9uQ,2269 | |||
| sqlalchemy/dialects/mysql/mariadb.py,sha256=kTfBLioLKk4JFFst4TY_iWqPtnvvQXFHknLfm89H2N8,853 | |||
| sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=sRlzRFU5E1512aFyEhmmL1ffeU1u5meaLqtRYTcrH7Y,8568 | |||
| sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=qiQdfLPze3QHuASAZ9iqRzD0hDW8FbKoQnfAEQCF7tM,5675 | |||
| sqlalchemy/dialects/mysql/mysqldb.py,sha256=zPHKMQbHu8bFuD7YvOJewn2axQbfwA93NZoeX3bbbiQ,9502 | |||
| sqlalchemy/dialects/mysql/provision.py,sha256=4oGkClQ8jC3YLPF54sB4kCjFc8HRTwf5zl5zftAAXGo,3474 | |||
| sqlalchemy/dialects/mysql/pymysql.py,sha256=GUnSHd2M2uKjmN46Hheymtm26g7phEgwYOXrX0zLY8M,4083 | |||
| sqlalchemy/dialects/mysql/pyodbc.py,sha256=072crI4qVyPhajYvHnsfFeSrNjLFVPIjBQKo5uyz5yk,4297 | |||
| sqlalchemy/dialects/mysql/reflection.py,sha256=XXM8AGpaRTqDvuObg89Bzn_4h2ETG03viYBpWZJM3vc,22822 | |||
| sqlalchemy/dialects/mysql/reserved_words.py,sha256=ucKX2p2c3UnMq2ayZuOHuf73eXhu7SKsOsTlIN1Q83I,9258 | |||
| sqlalchemy/dialects/mysql/types.py,sha256=L5cTCsMT1pTedszNEM3jSxFNZEMcHQLprYCZ0vmfsnA,24343 | |||
| sqlalchemy/dialects/oracle/__init__.py,sha256=p4-2gw7TT0bX_MoJXTGD4i8WHctYsK9kCRbkpzykBrc,1493 | |||
| sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/__pycache__/types.cpython-310.pyc,, | |||
| sqlalchemy/dialects/oracle/base.py,sha256=-7b5iubFPxJyDRoLXlxj8rk8eBRN2_IdZlB2zzzrrbw,118246 | |||
| sqlalchemy/dialects/oracle/cx_oracle.py,sha256=t5yH4svVz7xoDSITF958blgZ01hbCUEWUKrAXwiCiAE,55566 | |||
| sqlalchemy/dialects/oracle/dictionary.py,sha256=7WMrbPkqo8ZdGjaEZyQr-5f2pajSOF1OTGb8P97z8-g,19519 | |||
| sqlalchemy/dialects/oracle/oracledb.py,sha256=UFcZwrrk0pWfAp_SKJZ1B5rIQHtNhOvuu73_JaSnTbI,9487 | |||
| sqlalchemy/dialects/oracle/provision.py,sha256=O9ZpF4OG6Cx4mMzLRfZwhs8dZjrJETWR402n9c7726A,8304 | |||
| sqlalchemy/dialects/oracle/types.py,sha256=QK3hJvWzKnnCe3oD3rItwEEIwcoBze8qGg7VFOvVlIk,8231 | |||
| sqlalchemy/dialects/postgresql/__init__.py,sha256=wwnNAq4wDQzrlPRzDNB06ayuq3L2HNO99nzeEvq-YcU,3892 | |||
| sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/array.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/json.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/__pycache__/types.cpython-310.pyc,, | |||
| sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=7TudtgsPiSB8O5kX8W8KxcNYR8t5h_UHb86b_ChL0P8,5696 | |||
| sqlalchemy/dialects/postgresql/array.py,sha256=bWcame7ntmI_Kx6gmBX0-chwADFdLHeCvaDQ4iX8id8,13734 | |||
| sqlalchemy/dialects/postgresql/asyncpg.py,sha256=e4Zv7hTb8OdwfvrM_B82GXgORBJeNQ2vChVfogvN7KI,40240 | |||
| sqlalchemy/dialects/postgresql/base.py,sha256=ed49Ode09deJF3jXr8V5sSHTwVoFVq0dlvNdP3mJaBQ,178989 | |||
| sqlalchemy/dialects/postgresql/dml.py,sha256=Pc69Le6qzmUHHb1FT5zeUSD31dWm6SBgdCAGW89cs3s,11212 | |||
| sqlalchemy/dialects/postgresql/ext.py,sha256=1bZ--iNh2O9ym7l2gXZX48yP3yMO4dqb9RpYro2Mj2Q,16262 | |||
| sqlalchemy/dialects/postgresql/hstore.py,sha256=otAx-RTDfpi_tcXkMuQV0JOIXtYgevgnsikLKKOkI6U,11541 | |||
| sqlalchemy/dialects/postgresql/json.py,sha256=9-ZvkLVMfQNSz0aHGml_B8sNg3gVq3-gxLnb5BO7Pog,11217 | |||
| sqlalchemy/dialects/postgresql/named_types.py,sha256=3IV1ufo7zJjKmX4VtGDEnoXE6xEqLJAtGG82IiqHXwY,17594 | |||
| sqlalchemy/dialects/postgresql/operators.py,sha256=NsAaWun_tL3d_be0fs9YL6T4LPKK6crnmFxxIJHgyeY,2808 | |||
| sqlalchemy/dialects/postgresql/pg8000.py,sha256=3yoekiWSF-xnaWMqG76XrYPMqerg-42TdmfsW_ivK9E,18640 | |||
| sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=hY3NXEUHxTWD4umhd2aowNu3laC-61Q_qQ_pReyXTUM,9254 | |||
| sqlalchemy/dialects/postgresql/provision.py,sha256=yqyx-aDFO9l2YcL9f4T5HBP_Lnt5dHsMjpuXUG8mi7A,5762 | |||
| sqlalchemy/dialects/postgresql/psycopg.py,sha256=3_xRoZ71mgTR-tYKDb6N0ebbrRD3dFwVRI5-ZuFFOg8,23378 | |||
| sqlalchemy/dialects/postgresql/psycopg2.py,sha256=DbCB1-90O8YQDaIR67RtKOjzW3nvrdHoiwnVWYv_mX4,31605 | |||
| sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=M7wAYSL6Pvt-4nbfacAHGyyw4XMKJ_bQZ1tc1pBtIdg,1756 | |||
| sqlalchemy/dialects/postgresql/ranges.py,sha256=6CgV7qkxEMJ9AQsiibo_XBLJYzGh-2ZxpG83sRaesVY,32949 | |||
| sqlalchemy/dialects/postgresql/types.py,sha256=Jfxqw9JaKNOq29JRWBublywgb3lLMyzx8YZI7CXpS2s,7300 | |||
| sqlalchemy/dialects/sqlite/__init__.py,sha256=lp9DIggNn349M-7IYhUA8et8--e8FRExWD2V_r1LJk4,1182 | |||
| sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/json.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-310.pyc,, | |||
| sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=OMvxP2eWyqk5beF-sHhzxRmjzO4VCQp55q7NH2XPVTE,12305 | |||
| sqlalchemy/dialects/sqlite/base.py,sha256=lUtigjn7NdPBq831zQsLcBwdwRJqdgKM_tUaDrMElOE,96794 | |||
| sqlalchemy/dialects/sqlite/dml.py,sha256=9GE55WvwoktKy2fHeT-Wbc9xPHgsbh5oBfd_fckMH5Q,8443 | |||
| sqlalchemy/dialects/sqlite/json.py,sha256=Eoplbb_4dYlfrtmQaI8Xddd2suAIHA-IdbDQYM-LIhs,2777 | |||
| sqlalchemy/dialects/sqlite/provision.py,sha256=UCpmwxf4IWlrpb2eLHGbPTpCFVbdI_KAh2mKtjiLYao,5632 | |||
| sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=OL2S_05DK9kllZj6DOz7QtEl7jI7syxjW6woS725ii4,5356 | |||
| sqlalchemy/dialects/sqlite/pysqlite.py,sha256=USPhTjA19ks7VfzjlMXN_BkUpZiPH8J5ZyR4B9DBhVY,28045 | |||
| sqlalchemy/dialects/type_migration_guidelines.txt,sha256=-uHNdmYFGB7bzUNT6i8M5nb4j6j9YUKAtW4lcBZqsMg,8239 | |||
| sqlalchemy/engine/__init__.py,sha256=Stb2oV6l8w65JvqEo6J4qtKoApcmOpXy3AAxQud4C1o,2818 | |||
| sqlalchemy/engine/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/_py_processors.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/_py_row.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/_py_util.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/characteristics.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/create.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/cursor.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/default.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/events.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/interfaces.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/mock.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/processors.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/reflection.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/result.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/row.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/strategies.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/url.cpython-310.pyc,, | |||
| sqlalchemy/engine/__pycache__/util.cpython-310.pyc,, | |||
| sqlalchemy/engine/_py_processors.py,sha256=j9i_lcYYQOYJMcsDerPxI0sVFBIlX5sqoYMdMJlgWPI,3744 | |||
| sqlalchemy/engine/_py_row.py,sha256=wSqoUFzLOJ1f89kgDb6sJm9LUrF5LMFpXPcK1vUsKcs,3787 | |||
| sqlalchemy/engine/_py_util.py,sha256=f2DI3AN1kv6EplelowesCVpwS8hSXNufRkZoQmJtSH8,2484 | |||
| sqlalchemy/engine/base.py,sha256=0jDuJYb1tipD-N9chnMCvd25JySJQCm8CDCt_lobmv0,122898 | |||
| sqlalchemy/engine/characteristics.py,sha256=N3kbvw_ApMh86wb5yAGnxtPYD4YRhYMWion1H_aVZBI,4765 | |||
| sqlalchemy/engine/create.py,sha256=mYJtOG2ZKM8sgyfjpGpamW15RDU7JXi5s6iibbJHMIs,33206 | |||
| sqlalchemy/engine/cursor.py,sha256=cFq61yrw76k-QR_xNUBWuL-Zeyb14ltG-6jo2Q2iuuw,76392 | |||
| sqlalchemy/engine/default.py,sha256=B3i9Ce3Jk64CtNaS1gnes4uCMhiQn2cMwnZh5RxR-aE,84137 | |||
| sqlalchemy/engine/events.py,sha256=c0unNFFiHzTAvkUtXoJaxzMFMDwurBkHiiUhuN8qluc,37381 | |||
| sqlalchemy/engine/interfaces.py,sha256=n6G7VW5kaGyBlKdCkgaDs694tfzLPOxZeboDmVaJThg,112832 | |||
| sqlalchemy/engine/mock.py,sha256=yvpxgFmRw5G4QsHeF-ZwQGHKES-HqQOucTxFtN1uzdk,4179 | |||
| sqlalchemy/engine/processors.py,sha256=XyfINKbo-2fjN-mW55YybvFyQMOil50_kVqsunahkNs,2379 | |||
| sqlalchemy/engine/reflection.py,sha256=FlT5kPpKm7Lah50GNt5XcnlJWojTL3LD_x0SoCF9kfY,75127 | |||
| sqlalchemy/engine/result.py,sha256=j6BI4Wj2bziQNQG5OlG_Cm4KcNWY9AoYvTXVlJUU-D8,77603 | |||
| sqlalchemy/engine/row.py,sha256=9AAQo9zYDL88GcZ3bjcQTwMT-YIcuGTSMAyTfmBJ_yM,12032 | |||
| sqlalchemy/engine/strategies.py,sha256=DqFSWaXJPL-29Omot9O0aOcuGL8KmCGyOvnPGDkAJoE,442 | |||
| sqlalchemy/engine/url.py,sha256=8eWkUaIUyDExOcJ2D4xJXRcn4OY1GQJ3Q2duSX6UGAg,30784 | |||
| sqlalchemy/engine/util.py,sha256=bNirO8k1S8yOW61uNH-a9QrWtAJ9VGFgbiR0lk1lUQU,5682 | |||
| sqlalchemy/event/__init__.py,sha256=KBrp622xojnC3FFquxa2JsMamwAbfkvzfv6Op0NKiYc,997 | |||
| sqlalchemy/event/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/event/__pycache__/api.cpython-310.pyc,, | |||
| sqlalchemy/event/__pycache__/attr.cpython-310.pyc,, | |||
| sqlalchemy/event/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/event/__pycache__/legacy.cpython-310.pyc,, | |||
| sqlalchemy/event/__pycache__/registry.cpython-310.pyc,, | |||
| sqlalchemy/event/api.py,sha256=DtDVgjKSorOfp9MGJ7fgMWrj4seC_hkwF4D8CW1RFZU,8226 | |||
| sqlalchemy/event/attr.py,sha256=X8QeHGK4ioSYht1vkhc11f606_mq_t91jMNIT314ubs,20751 | |||
| sqlalchemy/event/base.py,sha256=3n9FmUkcXYHHyGzfpjKDsrIUVCNST_hq4zOtrNm0_a4,14954 | |||
| sqlalchemy/event/legacy.py,sha256=teMPs00fO-4g8a_z2omcVKkYce5wj_1uvJO2n2MIeuo,8227 | |||
| sqlalchemy/event/registry.py,sha256=nfTSSyhjZZXc5wseWB4sXn-YibSc0LKX8mg17XlWmAo,10835 | |||
| sqlalchemy/events.py,sha256=k-ZD38aSPD29LYhED7CBqttp5MDVVx_YSaWC2-cu9ec,525 | |||
| sqlalchemy/exc.py,sha256=M_8-O1hd8i6gbyx-TapV400p_Lxq2QqTGMXUAO-YgCc,23976 | |||
| sqlalchemy/ext/__init__.py,sha256=S1fGKAbycnQDV01gs-JWGaFQ9GCD4QHwKcU2wnugg_o,322 | |||
| sqlalchemy/ext/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/associationproxy.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/automap.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/baked.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/compiler.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/horizontal_shard.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/hybrid.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/indexable.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/instrumentation.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/mutable.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/orderinglist.cpython-310.pyc,, | |||
| sqlalchemy/ext/__pycache__/serializer.cpython-310.pyc,, | |||
| sqlalchemy/ext/associationproxy.py,sha256=5O5ANHARO8jytvqBQmOu-QjNVE4Hh3tfYquqKAj5ajs,65771 | |||
| sqlalchemy/ext/asyncio/__init__.py,sha256=1OqSxEyIUn7RWLGyO12F-jAUIvk1I6DXlVy80-Gvkds,1317 | |||
| sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/engine.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/exc.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/result.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/__pycache__/session.cpython-310.pyc,, | |||
| sqlalchemy/ext/asyncio/base.py,sha256=fl7wxZD9KjgFiCtG3WXrYjHEvanamcsodCqq9pH9lOk,8905 | |||
| sqlalchemy/ext/asyncio/engine.py,sha256=S_IRWX4QAjj2veLSu4Y3gKBIXkKQt7_2StJAK2_KUDY,48190 | |||
| sqlalchemy/ext/asyncio/exc.py,sha256=8sII7VMXzs2TrhizhFQMzSfcroRtiesq8o3UwLfXSgQ,639 | |||
| sqlalchemy/ext/asyncio/result.py,sha256=ID2eh-NHW-lnNFTxbKhje8fr-tnsucUsiw_jcpGcSPc,30409 | |||
| sqlalchemy/ext/asyncio/scoping.py,sha256=BmE1UbFV_C4BXB4WngJc523DeMH-nTchNb8ORiSPYfE,52597 | |||
| sqlalchemy/ext/asyncio/session.py,sha256=ZZklu124Rm2p9B5pbcDbR0zVyEuq-rn69_ltByuKNXo,63092 | |||
| sqlalchemy/ext/automap.py,sha256=cMs3mMXZBbfQXahxHbT3-6h5uMM--GZIcdtwhRrZoio,61589 | |||
| sqlalchemy/ext/baked.py,sha256=H6T1il7GY84BhzPFj49UECSpZh_eBuiHomA-QIsYOYQ,17807 | |||
| sqlalchemy/ext/compiler.py,sha256=ONPoxoKD2yUS9R2-oOhmPsA7efm-Bs0BXo7HE1dGlsU,20391 | |||
| sqlalchemy/ext/declarative/__init__.py,sha256=20psLdFQbbOWfpdXHZ0CTY6I1k4UqXvKemNVu1LvPOI,1818 | |||
| sqlalchemy/ext/declarative/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/ext/declarative/__pycache__/extensions.cpython-310.pyc,, | |||
| sqlalchemy/ext/declarative/extensions.py,sha256=uCjN1GisQt54AjqYnKYzJdUjnGd2pZBW47WWdPlS7FE,19547 | |||
| sqlalchemy/ext/horizontal_shard.py,sha256=wuwAPnHymln0unSBnyx-cpX0AfESKSsypaSQTYCvzDk,16750 | |||
| sqlalchemy/ext/hybrid.py,sha256=IYkCaPZ29gm2cPKPg0cWMkLCEqMykD8-JJTvgacGbmc,52458 | |||
| sqlalchemy/ext/indexable.py,sha256=UkTelbydKCdKelzbv3HWFFavoET9WocKaGRPGEOVfN8,11032 | |||
| sqlalchemy/ext/instrumentation.py,sha256=sg8ghDjdHSODFXh_jAmpgemnNX1rxCeeXEG3-PMdrNk,15707 | |||
| sqlalchemy/ext/mutable.py,sha256=L5ZkHBGYhMaqO75Xtyrk2DBR44RDk0g6Rz2HzHH0F8Q,37355 | |||
| sqlalchemy/ext/mypy/__init__.py,sha256=0WebDIZmqBD0OTq5JLtd_PmfF9JGxe4d4Qv3Ml3PKUg,241 | |||
| sqlalchemy/ext/mypy/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/apply.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/infer.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/names.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/plugin.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/__pycache__/util.cpython-310.pyc,, | |||
| sqlalchemy/ext/mypy/apply.py,sha256=Aek_-XA1eXihT4attxhfE43yBKtCgsxBSb--qgZKUqc,10550 | |||
| sqlalchemy/ext/mypy/decl_class.py,sha256=1vVJRII2apnLTUbc5HkJS6Z2GueaUv_eKvhbqh7Wik4,17384 | |||
| sqlalchemy/ext/mypy/infer.py,sha256=KVnmLFEVS33Al8pUKI7MJbJQu3KeveBUMl78EluBORw,19369 | |||
| sqlalchemy/ext/mypy/names.py,sha256=Q3ef8XQBgVm9WUwlItqlYCXDNi_kbV5DdLEgbtEMEI8,10479 | |||
| sqlalchemy/ext/mypy/plugin.py,sha256=74ML8LI9xar0V86oCxnPFv5FQGEEfUzK64vOay4BKFs,9750 | |||
| sqlalchemy/ext/mypy/util.py,sha256=1zuDQG8ezmF-XhJmAQU_lcBHiD--sL-lq20clg8t4lE,9448 | |||
| sqlalchemy/ext/orderinglist.py,sha256=TGYbsGH72wEZcFNQDYDsZg9OSPuzf__P8YX8_2HtYUo,14384 | |||
| sqlalchemy/ext/serializer.py,sha256=YemanWdeMVUDweHCnQc-iMO6mVVXNo2qQ5NK0Eb2_Es,6178 | |||
| sqlalchemy/future/__init__.py,sha256=q2mw-gxk_xoxJLEvRoyMha3vO1xSRHrslcExOHZwmPA,512 | |||
| sqlalchemy/future/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/future/__pycache__/engine.cpython-310.pyc,, | |||
| sqlalchemy/future/engine.py,sha256=AgIw6vMsef8W6tynOTkxsjd6o_OQDwGjLdbpoMD8ue8,495 | |||
| sqlalchemy/inspection.py,sha256=MF-LE358wZDUEl1IH8-Uwt2HI65EsQpQW5o5udHkZwA,5063 | |||
| sqlalchemy/log.py,sha256=8x9UR3nj0uFm6or6bQF-JWb4fYv2zOeQjG_w-0wOJFA,8607 | |||
| sqlalchemy/orm/__init__.py,sha256=ZYys5nL3RFUDCMOLFDBrRI52F6er3S1U1OY9TeORuKs,8463 | |||
| sqlalchemy/orm/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/_orm_constructors.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/_typing.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/attributes.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/bulk_persistence.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/clsregistry.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/collections.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/context.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/decl_api.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/decl_base.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/dependency.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/descriptor_props.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/dynamic.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/evaluator.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/events.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/exc.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/identity.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/instrumentation.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/interfaces.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/loading.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/mapped_collection.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/mapper.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/path_registry.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/persistence.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/properties.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/query.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/relationships.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/scoping.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/session.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/state.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/state_changes.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/strategies.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/strategy_options.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/sync.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/unitofwork.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/util.cpython-310.pyc,, | |||
| sqlalchemy/orm/__pycache__/writeonly.cpython-310.pyc,, | |||
| sqlalchemy/orm/_orm_constructors.py,sha256=tlTZoY87CFSBpnw7iIYKTCeofjLg-r2ibfCfMfbyEMU,99461 | |||
| sqlalchemy/orm/_typing.py,sha256=DVBfpHmDVK4x1zxaGJPY2GoTrAsyR6uexv20Lzf1afc,4973 | |||
| sqlalchemy/orm/attributes.py,sha256=wGpyY9aIxWRkL3y6qlbUgIW78rGA4X38ZhOaWI9S818,92535 | |||
| sqlalchemy/orm/base.py,sha256=re6A8ooMxLyfCAeQfhBwwxeJQkiH7EtzzOZIVIiTPlw,27466 | |||
| sqlalchemy/orm/bulk_persistence.py,sha256=wU204OQK-V6rJfpc3HPeMAA1X9vZg9nSNpI1zKuZtcY,70086 | |||
| sqlalchemy/orm/clsregistry.py,sha256=29LyYiuj0qbebOpgW6DbBPNB2ikTweFQar1byCst7I0,17958 | |||
| sqlalchemy/orm/collections.py,sha256=jpMsJGVixmrW9kfT8wevm9kpatKRqyDLcqWd7CjKPxE,52179 | |||
| sqlalchemy/orm/context.py,sha256=pFFc-1ZjaV3aQMAO27UIh0W6NyLc30n2IbuWNprMtS8,112445 | |||
| sqlalchemy/orm/decl_api.py,sha256=_WPKQ_vSE5k2TLtNmkaxxYmvbhZvkRMrrvCeDxdqDQE,63998 | |||
| sqlalchemy/orm/decl_base.py,sha256=Tq6I3Jm3bkM01mmoiHfdFXLE94YDk1ik2u2dXL1RxLc,81601 | |||
| sqlalchemy/orm/dependency.py,sha256=hgjksUWhgbmgHK5GdJdiDCBgDAIGQXIrY-Tj79tbL2k,47631 | |||
| sqlalchemy/orm/descriptor_props.py,sha256=pKtpP7H1LB_YuHRVrEYpfFZybEnUUdPwQXxduYFe2hA,37180 | |||
| sqlalchemy/orm/dynamic.py,sha256=jksBDCOsm6EBMVParcNGuMeaAv12hX4IzouKspC-HPA,9786 | |||
| sqlalchemy/orm/evaluator.py,sha256=q292K5vdpP69G7Z9y1RqI5GFAk2diUPwnsXE8De_Wgw,11925 | |||
| sqlalchemy/orm/events.py,sha256=0lxP-EluVWSUr07ny2nsuQ8QhrDu9Qc4ON6QRUhuJnA,127703 | |||
| sqlalchemy/orm/exc.py,sha256=IP40P-wOeXhkYk0YizuTC3wqm6W9cPTaQU08f5MMaQ0,7413 | |||
| sqlalchemy/orm/identity.py,sha256=jHdCxCpCyda_8mFOfGmN_Pr0XZdKiU-2hFZshlNxbHs,9249 | |||
| sqlalchemy/orm/instrumentation.py,sha256=M-kZmkUvHUxtf-0mCA8RIM5QmMH1hWlYR_pKMwaidjA,24321 | |||
| sqlalchemy/orm/interfaces.py,sha256=Hmf1BjbfOarZRgMlruqghR7cgH2xyugA9v5t0x-a-wU,48502 | |||
| sqlalchemy/orm/loading.py,sha256=9RacpzFOWbuKgPRWHFmyIvD4fYCLAnkpwBFASyQ2CoI,58277 | |||
| sqlalchemy/orm/mapped_collection.py,sha256=3cneB1dfPTLrsTvKoo9_oCY2xtq4UAHfe5WSXPyqIS4,19690 | |||
| sqlalchemy/orm/mapper.py,sha256=bfoRzNKKnjF-CDvr2Df7HZC9TepvtuQ49LRz_fW7DGQ,171088 | |||
| sqlalchemy/orm/path_registry.py,sha256=sJZMv_WPqUpHfQtKWaX3WYFeKBcNJ8C3wOM2mkBGkTE,25920 | |||
| sqlalchemy/orm/persistence.py,sha256=dzyB2JOXNwQgaCbN8kh0sEz00WFePr48qf8NWVCUZH8,61701 | |||
| sqlalchemy/orm/properties.py,sha256=eDPFzxYUgdM3uWjHywnb1XW-i0tVKKyx7A2MCD31GQU,29306 | |||
| sqlalchemy/orm/query.py,sha256=qKuFTUlbGVjs84AQ7APBY0PJhrzc2JJ1upeI658MV_o,117596 | |||
| sqlalchemy/orm/relationships.py,sha256=dS5SY0v1MiD7iCNnAQlHaI6prUQhL5EkXT7ijc8FR8E,128644 | |||
| sqlalchemy/orm/scoping.py,sha256=gFYywLeMmd5qjFdVPzeuCX727mTaChrCv8aqn4wPke0,78677 | |||
| sqlalchemy/orm/session.py,sha256=Da-wd01Rw5fCqRr05RBY3dath-ssdloB4BvVrTVy8LA,195250 | |||
| sqlalchemy/orm/state.py,sha256=mW2f1hMSNeTJ89foutOE1EqLLD6DZkrSeO-pgagZweg,37520 | |||
| sqlalchemy/orm/state_changes.py,sha256=qKYg7NxwrDkuUY3EPygAztym6oAVUFcP2wXn7QD3Mz4,6815 | |||
| sqlalchemy/orm/strategies.py,sha256=OtmMtWpCDk4ZiaM_ipzGn80sPOi6Opwj3Co4lUHpd_w,114206 | |||
| sqlalchemy/orm/strategy_options.py,sha256=tFDJllL8Evxo2UjpyMkrxxRdphaclM-FBUd9O8WArdY,85366 | |||
| sqlalchemy/orm/sync.py,sha256=g7iZfSge1HgxMk9SKRgUgtHEbpbZ1kP_CBqOIdTOXqc,5779 | |||
| sqlalchemy/orm/unitofwork.py,sha256=fiVaqcymbDDHRa1NjS90N9Z466nd5pkJOEi1dHO6QLY,27033 | |||
| sqlalchemy/orm/util.py,sha256=PvU_J4PSNcaOoNnsLY9qZ0lcElG71ykvdZSDZ76WSwg,80660 | |||
| sqlalchemy/orm/writeonly.py,sha256=SYu2sAaHZONk2pW4PmtE871LG-O0P_bjidvKzY1H_zI,22305 | |||
| sqlalchemy/pool/__init__.py,sha256=qiDdq4r4FFAoDrK6ncugF_i6usi_X1LeJt-CuBHey0s,1804 | |||
| sqlalchemy/pool/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/pool/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/pool/__pycache__/events.cpython-310.pyc,, | |||
| sqlalchemy/pool/__pycache__/impl.cpython-310.pyc,, | |||
| sqlalchemy/pool/base.py,sha256=WF4az4ZKuzQGuKeSJeyexaYjmWZUvYdC6KIi8zTGodw,52236 | |||
| sqlalchemy/pool/events.py,sha256=xGjkIUZl490ZDtCHqnQF9ZCwe2Jv93eGXmnQxftB11E,13147 | |||
| sqlalchemy/pool/impl.py,sha256=JwpALSkH-pCoO_6oENbkHYY00Jx9nlttyoI61LivRNc,18944 | |||
| sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| sqlalchemy/schema.py,sha256=dKiWmgHYjcKQ4TiiD6vD0UMmIsD8u0Fsor1M9AAeGUs,3194 | |||
| sqlalchemy/sql/__init__.py,sha256=UNa9EUiYWoPayf-FzNcwVgQvpsBdInPZfpJesAStN9o,5820 | |||
| sqlalchemy/sql/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_dml_constructors.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_elements_constructors.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_orm_types.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_py_util.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/_typing.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/annotation.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/cache_key.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/coercions.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/compiler.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/crud.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/ddl.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/default_comparator.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/dml.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/elements.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/events.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/expression.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/functions.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/lambdas.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/naming.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/operators.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/roles.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/schema.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/selectable.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/sqltypes.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/traversals.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/type_api.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/util.cpython-310.pyc,, | |||
| sqlalchemy/sql/__pycache__/visitors.cpython-310.pyc,, | |||
| sqlalchemy/sql/_dml_constructors.py,sha256=YdBJex0MCVACv4q2nl_ii3uhxzwU6aDB8zAsratX5UQ,3867 | |||
| sqlalchemy/sql/_elements_constructors.py,sha256=1SX6o1ezeB8C9DAa2m0WxmfhM3ji3FeCprXFQkNerNY,63048 | |||
| sqlalchemy/sql/_orm_types.py,sha256=T-vjcry4C1y0GToFKVxQCnmly_-Zsq4IO4SHN6bvUF4,625 | |||
| sqlalchemy/sql/_py_util.py,sha256=hiM9ePbRSGs60bAMxPFuJCIC_p9SQ1VzqXGiPchiYwE,2173 | |||
| sqlalchemy/sql/_selectable_constructors.py,sha256=wjE6HrLm9cR7bxvZXT8sFLUqT6t_J9G1XyQCnYmBDl0,18780 | |||
| sqlalchemy/sql/_typing.py,sha256=oqwrYHVMtK-AuKGH9c4SgfiOEJUt5vjkzSEzzscMHkM,12771 | |||
| sqlalchemy/sql/annotation.py,sha256=aqbbVz9kfbCT3_66CZ9GEirVN197Cukoqt8rq48FgkQ,18245 | |||
| sqlalchemy/sql/base.py,sha256=Lg7iHywXmB7XuRMar45XSM4KayChGhfj1c8E7nWUJdo,73899 | |||
| sqlalchemy/sql/cache_key.py,sha256=0Db8mR8IrpBgdzXs4TGTt98LOpL3c7KABd72MAPKUQQ,33668 | |||
| sqlalchemy/sql/coercions.py,sha256=1xzN_9U5BCJGgokdc5iYj5o2cMAfEEZkr1Oa9Q-JYj8,40493 | |||
| sqlalchemy/sql/compiler.py,sha256=e5XWUmeZnE3O8r7fndfmXjAze3qlFZY223BHqrso0AY,274647 | |||
| sqlalchemy/sql/crud.py,sha256=g9xcol2KRGjZi1qsb2-bVz8zgVy_53gfMtJcnNO2vyQ,56521 | |||
| sqlalchemy/sql/ddl.py,sha256=CIqMilCKfuQnF0lrZsQdTxgrbXqcTauKr0Ojzj77PFQ,45602 | |||
| sqlalchemy/sql/default_comparator.py,sha256=utXWsZVGEjflhFfCT4ywa6RnhORc1Rryo87Hga71Rps,16707 | |||
| sqlalchemy/sql/dml.py,sha256=pn0Lm1ofC5qVZzwGWFW73lPCiNba8OsTeemurJgwRyg,65614 | |||
| sqlalchemy/sql/elements.py,sha256=coAHu2qUNrEKOv0ZvnDsc3G8_6o9lciylZn-K6fJDXU,174621 | |||
| sqlalchemy/sql/events.py,sha256=iC_Q1Htm1Aobt5tOYxWfHHqNpoytrULORmUKcusH_-E,18290 | |||
| sqlalchemy/sql/expression.py,sha256=VMX-dLpsZYnVRJpYNDozDUgaj7iQ0HuewUKVefD57PE,7586 | |||
| sqlalchemy/sql/functions.py,sha256=kMMYplvuIHFAPwxBI03SizwaLcYEHzysecWk-R1V-JM,63762 | |||
| sqlalchemy/sql/lambdas.py,sha256=DP0Qz7Ypo8QhzMwygGHYgRhwJMx-rNezO1euouH3iYU,49292 | |||
| sqlalchemy/sql/naming.py,sha256=ZHs1qSV3ou8TYmZ92uvU3sfdklUQlIz4uhe330n05SU,6858 | |||
| sqlalchemy/sql/operators.py,sha256=r4oQp4h5zTMFFOpiFNV56joIK-QIjJCobatsmaZ-724,75935 | |||
| sqlalchemy/sql/roles.py,sha256=pOsVn_OZD7mF2gJByHf24Rjopt0_Hu3dUCEOK5t4KS8,7662 | |||
| sqlalchemy/sql/schema.py,sha256=GctEXTYd5R9nqS07xgf5uFC-_3UIZJ7q3RaetzSs0Pk,229225 | |||
| sqlalchemy/sql/selectable.py,sha256=XHL0xJeLWnQeGG_dK7zQ8lNsCGdP8DeJQuCF4bCW-bo,233505 | |||
| sqlalchemy/sql/sqltypes.py,sha256=AAXED0rebCNmrY8K8kL6zox8_GD0L_p0lRzwM3SxCGk,126076 | |||
| sqlalchemy/sql/traversals.py,sha256=NFgJrVJzInO3HrnG90CklxrDXhFydZohPs2vRJkh3Bo,33589 | |||
| sqlalchemy/sql/type_api.py,sha256=CJAvRix_rRoOsE77sH4BqeGX0lIdtk8dX-StjGx3mUo,83208 | |||
| sqlalchemy/sql/util.py,sha256=qGHQF-tPCj-m1FBerzT7weCanGcXU7dK5m-W7NHio-4,48077 | |||
| sqlalchemy/sql/visitors.py,sha256=71wdVvhhZL4nJvVwFAs6ssaW-qZgNRSmKjpAcOzF_TA,36317 | |||
| sqlalchemy/testing/__init__.py,sha256=VsrEHrORpAF5n7Vfl43YQgABh6EP1xBx_gHxs7pSXeE,3126 | |||
| sqlalchemy/testing/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/assertions.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/assertsql.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/asyncio.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/config.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/engines.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/entities.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/exclusions.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/pickleable.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/profiling.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/provision.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/requirements.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/schema.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/util.cpython-310.pyc,, | |||
| sqlalchemy/testing/__pycache__/warnings.cpython-310.pyc,, | |||
| sqlalchemy/testing/assertions.py,sha256=gL0rA7CCZJbcVgvWOPV91tTZTRwQc1_Ta0-ykBn83Ew,31439 | |||
| sqlalchemy/testing/assertsql.py,sha256=IgQG7l94WaiRP8nTbilJh1ZHZl125g7GPq-S5kmQZN0,16817 | |||
| sqlalchemy/testing/asyncio.py,sha256=kM8uuOqDBagZF0r9xvGmsiirUVLUQ_KBzjUFU67W-b8,3830 | |||
| sqlalchemy/testing/config.py,sha256=AqyH1qub_gDqX0BvlL-JBQe7N-t2wo8655FtwblUNOY,12090 | |||
| sqlalchemy/testing/engines.py,sha256=HFJceEBD3Q_TTFQMTtIV5wGWO_a7oUgoKtUF_z636SM,13481 | |||
| sqlalchemy/testing/entities.py,sha256=IphFegPKbff3Un47jY6bi7_MQXy6qkx_50jX2tHZJR4,3354 | |||
| sqlalchemy/testing/exclusions.py,sha256=T8B01hmm8WVs-EKcUOQRzabahPqblWJfOidi6bHJ6GA,12460 | |||
| sqlalchemy/testing/fixtures/__init__.py,sha256=dMClrIoxqlYIFpk2ia4RZpkbfxsS_3EBigr9QsPJ66g,1198 | |||
| sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/testing/fixtures/__pycache__/base.cpython-310.pyc,, | |||
| sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-310.pyc,, | |||
| sqlalchemy/testing/fixtures/__pycache__/orm.cpython-310.pyc,, | |||
| sqlalchemy/testing/fixtures/__pycache__/sql.cpython-310.pyc,, | |||
| sqlalchemy/testing/fixtures/base.py,sha256=9r_J2ksiTzClpUxW0TczICHrWR7Ny8PV8IsBz6TsGFI,12256 | |||
| sqlalchemy/testing/fixtures/mypy.py,sha256=gdxiwNFIzDlNGSOdvM3gbwDceVCC9t8oM5kKbwyhGBk,11973 | |||
| sqlalchemy/testing/fixtures/orm.py,sha256=8EFbnaBbXX_Bf4FcCzBUaAHgyVpsLGBHX16SGLqE3Fg,6095 | |||
| sqlalchemy/testing/fixtures/sql.py,sha256=TE5q2BSOc_Vq1TPaLnj1F9ZA_YiQHlqyvUtGId9bGr0,15774 | |||
| sqlalchemy/testing/pickleable.py,sha256=U9mIqk-zaxq9Xfy7HErP7UrKgTov-A3QFnhZh-NiOjI,2833 | |||
| sqlalchemy/testing/plugin/__init__.py,sha256=79F--BIY_NTBzVRIlJGgAY5LNJJ3cD19XvrAo4X0W9A,247 | |||
| sqlalchemy/testing/plugin/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-310.pyc,, | |||
| sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-310.pyc,, | |||
| sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-310.pyc,, | |||
| sqlalchemy/testing/plugin/bootstrap.py,sha256=oYScMbEW4pCnWlPEAq1insFruCXFQeEVBwo__i4McpU,1685 | |||
| sqlalchemy/testing/plugin/plugin_base.py,sha256=BgNzWNEmgpK4CwhyblQQKnH-7FDKVi_Uul5vw8fFjBU,21578 | |||
| sqlalchemy/testing/plugin/pytestplugin.py,sha256=6jkQHH2VQMD75k2As9CuWXmEy9jrscoFRhCNg6-PaTw,27656 | |||
| sqlalchemy/testing/profiling.py,sha256=PbuPhRFbauFilUONeY3tV_Y_5lBkD7iCa8VVyH2Sk9Y,10148 | |||
| sqlalchemy/testing/provision.py,sha256=zXsw2D2Xpmw_chmYLsE1GXQqKQ-so3V8xU_joTcKan0,14619 | |||
| sqlalchemy/testing/requirements.py,sha256=N9pSj7z2wVMkBif-DQfPVa_cl9k6p9g_J5FY1OsWtrY,51817 | |||
| sqlalchemy/testing/schema.py,sha256=lr4GkGrGwagaHMuSGzWdzkMaj3HnS7dgfLLWfxt__-U,6513 | |||
| sqlalchemy/testing/suite/__init__.py,sha256=Y5DRNG0Yl1u3ypt9zVF0Z9suPZeuO_UQGLl-wRgvTjU,722 | |||
| sqlalchemy/testing/suite/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_cte.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_insert.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_results.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_select.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_types.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-310.pyc,, | |||
| sqlalchemy/testing/suite/test_cte.py,sha256=6zBC3W2OwX1Xs-HedzchcKN2S7EaLNkgkvV_JSZ_Pq0,6451 | |||
| sqlalchemy/testing/suite/test_ddl.py,sha256=1Npkf0C_4UNxphthAGjG078n0vPEgnSIHpDu5MfokxQ,12031 | |||
| sqlalchemy/testing/suite/test_deprecations.py,sha256=BcJxZTcjYqeOAENVElCg3hVvU6fkGEW3KGBMfnW8bng,5337 | |||
| sqlalchemy/testing/suite/test_dialect.py,sha256=EH4ZQWbnGdtjmx5amZtTyhYmrkXJCvW1SQoLahoE7uk,22923 | |||
| sqlalchemy/testing/suite/test_insert.py,sha256=9azifj6-OCD7s8h_tAO1uPw100ibQv8YoKc_VA3hn3c,18824 | |||
| sqlalchemy/testing/suite/test_reflection.py,sha256=tJSbJFg5fw0sSUv3I_FPmhN7rWWeJtq3YyxmylWJUlM,106466 | |||
| sqlalchemy/testing/suite/test_results.py,sha256=NQ23m8FDVd0ub751jN4PswGoAhk5nrqvjHvpYULZXnc,15937 | |||
| sqlalchemy/testing/suite/test_rowcount.py,sha256=3KDTlRgjpQ1OVfp__1cv8Hvq4CsDKzmrhJQ_WIJWoJg,7900 | |||
| sqlalchemy/testing/suite/test_select.py,sha256=FvMFYQW9IJpDWGYZiJk46is6YrtmdSghBdTjZCG8T0Y,58574 | |||
| sqlalchemy/testing/suite/test_sequence.py,sha256=66bCoy4xo99GBSaX6Hxb88foANAykLGRz1YEKbvpfuA,9923 | |||
| sqlalchemy/testing/suite/test_types.py,sha256=rFmTOg6XuMch9L2-XthfLJRCTTwpZbMfrNss2g09gmc,65677 | |||
| sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=c3_eIxLyORuSOhNDP0jWKxPyUf3SwMFpdalxtquwqlM,6141 | |||
| sqlalchemy/testing/suite/test_update_delete.py,sha256=yTiM2unnfOK9rK8ZkqeTTU_MkT-RsKFLmdYliniZfAY,3994 | |||
| sqlalchemy/testing/util.py,sha256=BFiSp3CEX95Dr-vv4l_7ZRu5vjZi9hjjnp-JKNfuS5E,14080 | |||
| sqlalchemy/testing/warnings.py,sha256=fJ-QJUY2zY2PPxZJKv9medW-BKKbCNbA4Ns_V3YwFXM,1546 | |||
| sqlalchemy/types.py,sha256=cQFM-hFRmaf1GErun1qqgEs6QxufvzMuwKqj9tuMPpE,3168 | |||
| sqlalchemy/util/__init__.py,sha256=B3bedg-LSQEscwqgmYYU-VENUX8_zAE3q9vb7tkfJNY,8277 | |||
| sqlalchemy/util/__pycache__/__init__.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/_collections.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/_has_cy.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/_py_collections.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/compat.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/concurrency.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/deprecations.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/langhelpers.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/preloaded.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/queue.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/tool_support.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/topological.cpython-310.pyc,, | |||
| sqlalchemy/util/__pycache__/typing.cpython-310.pyc,, | |||
| sqlalchemy/util/_collections.py,sha256=NE9dGJo8UNXIMbY3l3k8AO9BdPW04DlKTYraKCinchI,20063 | |||
| sqlalchemy/util/_concurrency_py3k.py,sha256=IBxZDTSyLbEx9I9ViQVXYP1twxM-pTa-3x_-U8snmLU,9191 | |||
| sqlalchemy/util/_has_cy.py,sha256=wCQmeSjT3jaH_oxfCEtGk-1g0gbSpt5MCK5UcWdMWqk,1247 | |||
| sqlalchemy/util/_py_collections.py,sha256=U6L5AoyLdgSv7cdqB4xxQbw1rpeJjyOZVXffgxgga8I,16714 | |||
| sqlalchemy/util/compat.py,sha256=R6bpBydldtbr6h7oJePihQxFb7jKiI-YDsK465MSOzk,8714 | |||
| sqlalchemy/util/concurrency.py,sha256=9lT_cMoO1fZNdY8QTUZ22oeSf-L5I-79Ke7chcBNPA0,3304 | |||
| sqlalchemy/util/deprecations.py,sha256=YBwvvYhSB8LhasIZRKvg_-WNoVhPUcaYI1ZrnjDn868,11971 | |||
| sqlalchemy/util/langhelpers.py,sha256=kT7kwtTOPzHUFs6PhM1IEoW5ioeuFUFg2pjHSbH4d94,64891 | |||
| sqlalchemy/util/preloaded.py,sha256=az7NmLJLsqs0mtM9uBkIu10-841RYDq8wOyqJ7xXvqE,5904 | |||
| sqlalchemy/util/queue.py,sha256=CaeSEaYZ57YwtmLdNdOIjT5PK_LCuwMFiO0mpp39ybM,10185 | |||
| sqlalchemy/util/tool_support.py,sha256=9braZyidaiNrZVsWtGmkSmus50-byhuYrlAqvhjcmnA,6135 | |||
| sqlalchemy/util/topological.py,sha256=N3M3Le7KzGHCmqPGg0ZBqixTDGwmFLhOZvBtc4rHL_g,3458 | |||
| sqlalchemy/util/typing.py,sha256=UctfgugSTnFJgZraikXJbB-5RoV7oyccZmbf2NeRfoA,16671 | |||
| @ -0,0 +1,6 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: bdist_wheel (0.43.0) | |||
| Root-Is-Purelib: false | |||
| Tag: cp310-cp310-manylinux_2_17_x86_64 | |||
| Tag: cp310-cp310-manylinux2014_x86_64 | |||
| @ -0,0 +1 @@ | |||
| sqlalchemy | |||
| @ -0,0 +1,132 @@ | |||
| import sys | |||
| import os | |||
| import re | |||
| import importlib | |||
| import warnings | |||
| is_pypy = '__pypy__' in sys.builtin_module_names | |||
| warnings.filterwarnings('ignore', | |||
| r'.+ distutils\b.+ deprecated', | |||
| DeprecationWarning) | |||
| def warn_distutils_present(): | |||
| if 'distutils' not in sys.modules: | |||
| return | |||
| if is_pypy and sys.version_info < (3, 7): | |||
| # PyPy for 3.6 unconditionally imports distutils, so bypass the warning | |||
| # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 | |||
| return | |||
| warnings.warn( | |||
| "Distutils was imported before Setuptools, but importing Setuptools " | |||
| "also replaces the `distutils` module in `sys.modules`. This may lead " | |||
| "to undesirable behaviors or errors. To avoid these issues, avoid " | |||
| "using distutils directly, ensure that setuptools is installed in the " | |||
| "traditional way (e.g. not an editable install), and/or make sure " | |||
| "that setuptools is always imported before distutils.") | |||
| def clear_distutils(): | |||
| if 'distutils' not in sys.modules: | |||
| return | |||
| warnings.warn("Setuptools is replacing distutils.") | |||
| mods = [name for name in sys.modules if re.match(r'distutils\b', name)] | |||
| for name in mods: | |||
| del sys.modules[name] | |||
| def enabled(): | |||
| """ | |||
| Allow selection of distutils by environment variable. | |||
| """ | |||
| which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib') | |||
| return which == 'local' | |||
| def ensure_local_distutils(): | |||
| clear_distutils() | |||
| # With the DistutilsMetaFinder in place, | |||
| # perform an import to cause distutils to be | |||
| # loaded from setuptools._distutils. Ref #2906. | |||
| add_shim() | |||
| importlib.import_module('distutils') | |||
| remove_shim() | |||
| # check that submodules load as expected | |||
| core = importlib.import_module('distutils.core') | |||
| assert '_distutils' in core.__file__, core.__file__ | |||
| def do_override(): | |||
| """ | |||
| Ensure that the local copy of distutils is preferred over stdlib. | |||
| See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 | |||
| for more motivation. | |||
| """ | |||
| if enabled(): | |||
| warn_distutils_present() | |||
| ensure_local_distutils() | |||
| class DistutilsMetaFinder: | |||
| def find_spec(self, fullname, path, target=None): | |||
| if path is not None: | |||
| return | |||
| method_name = 'spec_for_{fullname}'.format(**locals()) | |||
| method = getattr(self, method_name, lambda: None) | |||
| return method() | |||
| def spec_for_distutils(self): | |||
| import importlib.abc | |||
| import importlib.util | |||
| class DistutilsLoader(importlib.abc.Loader): | |||
| def create_module(self, spec): | |||
| return importlib.import_module('setuptools._distutils') | |||
| def exec_module(self, module): | |||
| pass | |||
| return importlib.util.spec_from_loader('distutils', DistutilsLoader()) | |||
| def spec_for_pip(self): | |||
| """ | |||
| Ensure stdlib distutils when running under pip. | |||
| See pypa/pip#8761 for rationale. | |||
| """ | |||
| if self.pip_imported_during_build(): | |||
| return | |||
| clear_distutils() | |||
| self.spec_for_distutils = lambda: None | |||
| @staticmethod | |||
| def pip_imported_during_build(): | |||
| """ | |||
| Detect if pip is being imported in a build script. Ref #2355. | |||
| """ | |||
| import traceback | |||
| return any( | |||
| frame.f_globals['__file__'].endswith('setup.py') | |||
| for frame, line in traceback.walk_stack(None) | |||
| ) | |||
| DISTUTILS_FINDER = DistutilsMetaFinder() | |||
| def add_shim(): | |||
| sys.meta_path.insert(0, DISTUTILS_FINDER) | |||
| def remove_shim(): | |||
| try: | |||
| sys.meta_path.remove(DISTUTILS_FINDER) | |||
| except ValueError: | |||
| pass | |||
| @ -0,0 +1 @@ | |||
| __import__('_distutils_hack').do_override() | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,20 @@ | |||
| Copyright 2010 Jason Kirtland | |||
| 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. | |||
| @ -0,0 +1,60 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: blinker | |||
| Version: 1.8.2 | |||
| Summary: Fast, simple object-to-object and broadcast signaling | |||
| Author: Jason Kirtland | |||
| Maintainer-email: Pallets Ecosystem <contact@palletsprojects.com> | |||
| Requires-Python: >=3.8 | |||
| Description-Content-Type: text/markdown | |||
| Classifier: Development Status :: 5 - Production/Stable | |||
| Classifier: License :: OSI Approved :: MIT License | |||
| Classifier: Programming Language :: Python | |||
| Classifier: Typing :: Typed | |||
| Project-URL: Chat, https://discord.gg/pallets | |||
| Project-URL: Documentation, https://blinker.readthedocs.io | |||
| Project-URL: Source, https://github.com/pallets-eco/blinker/ | |||
| # Blinker | |||
| Blinker provides a fast dispatching system that allows any number of | |||
| interested parties to subscribe to events, or "signals". | |||
| ## Pallets Community Ecosystem | |||
| > [!IMPORTANT]\ | |||
| > This project is part of the Pallets Community Ecosystem. Pallets is the open | |||
| > source organization that maintains Flask; Pallets-Eco enables community | |||
| > maintenance of related projects. If you are interested in helping maintain | |||
| > this project, please reach out on [the Pallets Discord server][discord]. | |||
| > | |||
| > [discord]: https://discord.gg/pallets | |||
| ## Example | |||
| Signal receivers can subscribe to specific senders or receive signals | |||
| sent by any sender. | |||
| ```pycon | |||
| >>> from blinker import signal | |||
| >>> started = signal('round-started') | |||
| >>> def each(round): | |||
| ... print(f"Round {round}") | |||
| ... | |||
| >>> started.connect(each) | |||
| >>> def round_two(round): | |||
| ... print("This is round two.") | |||
| ... | |||
| >>> started.connect(round_two, sender=2) | |||
| >>> for round in range(1, 4): | |||
| ... started.send(round) | |||
| ... | |||
| Round 1! | |||
| Round 2! | |||
| This is round two. | |||
| Round 3! | |||
| ``` | |||
| @ -0,0 +1,12 @@ | |||
| blinker-1.8.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| blinker-1.8.2.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054 | |||
| blinker-1.8.2.dist-info/METADATA,sha256=3tEx40hm9IEofyFqDPJsDPE9MAIEhtifapoSp7FqzuA,1633 | |||
| blinker-1.8.2.dist-info/RECORD,, | |||
| blinker-1.8.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 | |||
| blinker/__init__.py,sha256=ymyJY_PoTgBzaPgdr4dq-RRsGh7D-sYQIGMNp8Rx4qc,1577 | |||
| blinker/__pycache__/__init__.cpython-310.pyc,, | |||
| blinker/__pycache__/_utilities.cpython-310.pyc,, | |||
| blinker/__pycache__/base.cpython-310.pyc,, | |||
| blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675 | |||
| blinker/base.py,sha256=nIZJEtXQ8LLZZJrwVp2wQcdfCzDixvAHR9VpSWiyVcQ,22574 | |||
| blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| @ -0,0 +1,4 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: flit 3.9.0 | |||
| Root-Is-Purelib: true | |||
| Tag: py3-none-any | |||
| @ -0,0 +1,60 @@ | |||
| from __future__ import annotations | |||
| import typing as t | |||
| from .base import ANY | |||
| from .base import default_namespace | |||
| from .base import NamedSignal | |||
| from .base import Namespace | |||
| from .base import Signal | |||
| from .base import signal | |||
| __all__ = [ | |||
| "ANY", | |||
| "default_namespace", | |||
| "NamedSignal", | |||
| "Namespace", | |||
| "Signal", | |||
| "signal", | |||
| ] | |||
| def __getattr__(name: str) -> t.Any: | |||
| import warnings | |||
| if name == "__version__": | |||
| import importlib.metadata | |||
| warnings.warn( | |||
| "The '__version__' attribute is deprecated and will be removed in" | |||
| " Blinker 1.9.0. Use feature detection or" | |||
| " 'importlib.metadata.version(\"blinker\")' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return importlib.metadata.version("blinker") | |||
| if name == "receiver_connected": | |||
| from .base import _receiver_connected | |||
| warnings.warn( | |||
| "The global 'receiver_connected' signal is deprecated and will be" | |||
| " removed in Blinker 1.9. Use 'Signal.receiver_connected' and" | |||
| " 'Signal.receiver_disconnected' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return _receiver_connected | |||
| if name == "WeakNamespace": | |||
| from .base import _WeakNamespace | |||
| warnings.warn( | |||
| "'WeakNamespace' is deprecated and will be removed in Blinker 1.9." | |||
| " Use 'Namespace' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return _WeakNamespace | |||
| raise AttributeError(name) | |||
| @ -0,0 +1,64 @@ | |||
| from __future__ import annotations | |||
| import collections.abc as c | |||
| import inspect | |||
| import typing as t | |||
| from weakref import ref | |||
| from weakref import WeakMethod | |||
| T = t.TypeVar("T") | |||
| class Symbol: | |||
| """A constant symbol, nicer than ``object()``. Repeated calls return the | |||
| same instance. | |||
| >>> Symbol('foo') is Symbol('foo') | |||
| True | |||
| >>> Symbol('foo') | |||
| foo | |||
| """ | |||
| symbols: t.ClassVar[dict[str, Symbol]] = {} | |||
| def __new__(cls, name: str) -> Symbol: | |||
| if name in cls.symbols: | |||
| return cls.symbols[name] | |||
| obj = super().__new__(cls) | |||
| cls.symbols[name] = obj | |||
| return obj | |||
| def __init__(self, name: str) -> None: | |||
| self.name = name | |||
| def __repr__(self) -> str: | |||
| return self.name | |||
| def __getnewargs__(self) -> tuple[t.Any, ...]: | |||
| return (self.name,) | |||
| def make_id(obj: object) -> c.Hashable: | |||
| """Get a stable identifier for a receiver or sender, to be used as a dict | |||
| key or in a set. | |||
| """ | |||
| if inspect.ismethod(obj): | |||
| # The id of a bound method is not stable, but the id of the unbound | |||
| # function and instance are. | |||
| return id(obj.__func__), id(obj.__self__) | |||
| if isinstance(obj, (str, int)): | |||
| # Instances with the same value always compare equal and have the same | |||
| # hash, even if the id may change. | |||
| return obj | |||
| # Assume other types are not hashable but will always be the same instance. | |||
| return id(obj) | |||
| def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]: | |||
| if inspect.ismethod(obj): | |||
| return WeakMethod(obj, callback) # type: ignore[arg-type, return-value] | |||
| return ref(obj, callback) | |||
| @ -0,0 +1,621 @@ | |||
| from __future__ import annotations | |||
| import collections.abc as c | |||
| import typing as t | |||
| import warnings | |||
| import weakref | |||
| from collections import defaultdict | |||
| from contextlib import AbstractContextManager | |||
| from contextlib import contextmanager | |||
| from functools import cached_property | |||
| from inspect import iscoroutinefunction | |||
| from weakref import WeakValueDictionary | |||
| from ._utilities import make_id | |||
| from ._utilities import make_ref | |||
| from ._utilities import Symbol | |||
| if t.TYPE_CHECKING: | |||
| F = t.TypeVar("F", bound=c.Callable[..., t.Any]) | |||
| ANY = Symbol("ANY") | |||
| """Symbol for "any sender".""" | |||
| ANY_ID = 0 | |||
| class Signal: | |||
| """A notification emitter. | |||
| :param doc: The docstring for the signal. | |||
| """ | |||
| ANY = ANY | |||
| """An alias for the :data:`~blinker.ANY` sender symbol.""" | |||
| set_class: type[set[t.Any]] = set | |||
| """The set class to use for tracking connected receivers and senders. | |||
| Python's ``set`` is unordered. If receivers must be dispatched in the order | |||
| they were connected, an ordered set implementation can be used. | |||
| .. versionadded:: 1.7 | |||
| """ | |||
| @cached_property | |||
| def receiver_connected(self) -> Signal: | |||
| """Emitted at the end of each :meth:`connect` call. | |||
| The signal sender is the signal instance, and the :meth:`connect` | |||
| arguments are passed through: ``receiver``, ``sender``, and ``weak``. | |||
| .. versionadded:: 1.2 | |||
| """ | |||
| return Signal(doc="Emitted after a receiver connects.") | |||
| @cached_property | |||
| def receiver_disconnected(self) -> Signal: | |||
| """Emitted at the end of each :meth:`disconnect` call. | |||
| The sender is the signal instance, and the :meth:`disconnect` arguments | |||
| are passed through: ``receiver`` and ``sender``. | |||
| This signal is emitted **only** when :meth:`disconnect` is called | |||
| explicitly. This signal cannot be emitted by an automatic disconnect | |||
| when a weakly referenced receiver or sender goes out of scope, as the | |||
| instance is no longer be available to be used as the sender for this | |||
| signal. | |||
| An alternative approach is available by subscribing to | |||
| :attr:`receiver_connected` and setting up a custom weakref cleanup | |||
| callback on weak receivers and senders. | |||
| .. versionadded:: 1.2 | |||
| """ | |||
| return Signal(doc="Emitted after a receiver disconnects.") | |||
| def __init__(self, doc: str | None = None) -> None: | |||
| if doc: | |||
| self.__doc__ = doc | |||
| self.receivers: dict[ | |||
| t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any] | |||
| ] = {} | |||
| """The map of connected receivers. Useful to quickly check if any | |||
| receivers are connected to the signal: ``if s.receivers:``. The | |||
| structure and data is not part of the public API, but checking its | |||
| boolean value is. | |||
| """ | |||
| self.is_muted: bool = False | |||
| self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) | |||
| self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) | |||
| self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {} | |||
| def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F: | |||
| """Connect ``receiver`` to be called when the signal is sent by | |||
| ``sender``. | |||
| :param receiver: The callable to call when :meth:`send` is called with | |||
| the given ``sender``, passing ``sender`` as a positional argument | |||
| along with any extra keyword arguments. | |||
| :param sender: Any object or :data:`ANY`. ``receiver`` will only be | |||
| called when :meth:`send` is called with this sender. If ``ANY``, the | |||
| receiver will be called for any sender. A receiver may be connected | |||
| to multiple senders by calling :meth:`connect` multiple times. | |||
| :param weak: Track the receiver with a :mod:`weakref`. The receiver will | |||
| be automatically disconnected when it is garbage collected. When | |||
| connecting a receiver defined within a function, set to ``False``, | |||
| otherwise it will be disconnected when the function scope ends. | |||
| """ | |||
| receiver_id = make_id(receiver) | |||
| sender_id = ANY_ID if sender is ANY else make_id(sender) | |||
| if weak: | |||
| self.receivers[receiver_id] = make_ref( | |||
| receiver, self._make_cleanup_receiver(receiver_id) | |||
| ) | |||
| else: | |||
| self.receivers[receiver_id] = receiver | |||
| self._by_sender[sender_id].add(receiver_id) | |||
| self._by_receiver[receiver_id].add(sender_id) | |||
| if sender is not ANY and sender_id not in self._weak_senders: | |||
| # store a cleanup for weakref-able senders | |||
| try: | |||
| self._weak_senders[sender_id] = make_ref( | |||
| sender, self._make_cleanup_sender(sender_id) | |||
| ) | |||
| except TypeError: | |||
| pass | |||
| if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers: | |||
| try: | |||
| self.receiver_connected.send( | |||
| self, receiver=receiver, sender=sender, weak=weak | |||
| ) | |||
| except TypeError: | |||
| # TODO no explanation or test for this | |||
| self.disconnect(receiver, sender) | |||
| raise | |||
| if _receiver_connected.receivers and self is not _receiver_connected: | |||
| try: | |||
| _receiver_connected.send( | |||
| self, receiver_arg=receiver, sender_arg=sender, weak_arg=weak | |||
| ) | |||
| except TypeError: | |||
| self.disconnect(receiver, sender) | |||
| raise | |||
| return receiver | |||
| def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]: | |||
| """Connect the decorated function to be called when the signal is sent | |||
| by ``sender``. | |||
| The decorated function will be called when :meth:`send` is called with | |||
| the given ``sender``, passing ``sender`` as a positional argument along | |||
| with any extra keyword arguments. | |||
| :param sender: Any object or :data:`ANY`. ``receiver`` will only be | |||
| called when :meth:`send` is called with this sender. If ``ANY``, the | |||
| receiver will be called for any sender. A receiver may be connected | |||
| to multiple senders by calling :meth:`connect` multiple times. | |||
| :param weak: Track the receiver with a :mod:`weakref`. The receiver will | |||
| be automatically disconnected when it is garbage collected. When | |||
| connecting a receiver defined within a function, set to ``False``, | |||
| otherwise it will be disconnected when the function scope ends.= | |||
| .. versionadded:: 1.1 | |||
| """ | |||
| def decorator(fn: F) -> F: | |||
| self.connect(fn, sender, weak) | |||
| return fn | |||
| return decorator | |||
| @contextmanager | |||
| def connected_to( | |||
| self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY | |||
| ) -> c.Generator[None, None, None]: | |||
| """A context manager that temporarily connects ``receiver`` to the | |||
| signal while a ``with`` block executes. When the block exits, the | |||
| receiver is disconnected. Useful for tests. | |||
| :param receiver: The callable to call when :meth:`send` is called with | |||
| the given ``sender``, passing ``sender`` as a positional argument | |||
| along with any extra keyword arguments. | |||
| :param sender: Any object or :data:`ANY`. ``receiver`` will only be | |||
| called when :meth:`send` is called with this sender. If ``ANY``, the | |||
| receiver will be called for any sender. | |||
| .. versionadded:: 1.1 | |||
| """ | |||
| self.connect(receiver, sender=sender, weak=False) | |||
| try: | |||
| yield None | |||
| finally: | |||
| self.disconnect(receiver) | |||
| @contextmanager | |||
| def muted(self) -> c.Generator[None, None, None]: | |||
| """A context manager that temporarily disables the signal. No receivers | |||
| will be called if the signal is sent, until the ``with`` block exits. | |||
| Useful for tests. | |||
| """ | |||
| self.is_muted = True | |||
| try: | |||
| yield None | |||
| finally: | |||
| self.is_muted = False | |||
| def temporarily_connected_to( | |||
| self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY | |||
| ) -> AbstractContextManager[None]: | |||
| """Deprecated alias for :meth:`connected_to`. | |||
| .. deprecated:: 1.1 | |||
| Renamed to ``connected_to``. Will be removed in Blinker 1.9. | |||
| .. versionadded:: 0.9 | |||
| """ | |||
| warnings.warn( | |||
| "'temporarily_connected_to' is renamed to 'connected_to'. The old name is" | |||
| " deprecated and will be removed in Blinker 1.9.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return self.connected_to(receiver, sender) | |||
| def send( | |||
| self, | |||
| sender: t.Any | None = None, | |||
| /, | |||
| *, | |||
| _async_wrapper: c.Callable[ | |||
| [c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any] | |||
| ] | |||
| | None = None, | |||
| **kwargs: t.Any, | |||
| ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: | |||
| """Call all receivers that are connected to the given ``sender`` | |||
| or :data:`ANY`. Each receiver is called with ``sender`` as a positional | |||
| argument along with any extra keyword arguments. Return a list of | |||
| ``(receiver, return value)`` tuples. | |||
| The order receivers are called is undefined, but can be influenced by | |||
| setting :attr:`set_class`. | |||
| If a receiver raises an exception, that exception will propagate up. | |||
| This makes debugging straightforward, with an assumption that correctly | |||
| implemented receivers will not raise. | |||
| :param sender: Call receivers connected to this sender, in addition to | |||
| those connected to :data:`ANY`. | |||
| :param _async_wrapper: Will be called on any receivers that are async | |||
| coroutines to turn them into sync callables. For example, could run | |||
| the receiver with an event loop. | |||
| :param kwargs: Extra keyword arguments to pass to each receiver. | |||
| .. versionchanged:: 1.7 | |||
| Added the ``_async_wrapper`` argument. | |||
| """ | |||
| if self.is_muted: | |||
| return [] | |||
| results = [] | |||
| for receiver in self.receivers_for(sender): | |||
| if iscoroutinefunction(receiver): | |||
| if _async_wrapper is None: | |||
| raise RuntimeError("Cannot send to a coroutine function.") | |||
| result = _async_wrapper(receiver)(sender, **kwargs) | |||
| else: | |||
| result = receiver(sender, **kwargs) | |||
| results.append((receiver, result)) | |||
| return results | |||
| async def send_async( | |||
| self, | |||
| sender: t.Any | None = None, | |||
| /, | |||
| *, | |||
| _sync_wrapper: c.Callable[ | |||
| [c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]] | |||
| ] | |||
| | None = None, | |||
| **kwargs: t.Any, | |||
| ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: | |||
| """Await all receivers that are connected to the given ``sender`` | |||
| or :data:`ANY`. Each receiver is called with ``sender`` as a positional | |||
| argument along with any extra keyword arguments. Return a list of | |||
| ``(receiver, return value)`` tuples. | |||
| The order receivers are called is undefined, but can be influenced by | |||
| setting :attr:`set_class`. | |||
| If a receiver raises an exception, that exception will propagate up. | |||
| This makes debugging straightforward, with an assumption that correctly | |||
| implemented receivers will not raise. | |||
| :param sender: Call receivers connected to this sender, in addition to | |||
| those connected to :data:`ANY`. | |||
| :param _sync_wrapper: Will be called on any receivers that are sync | |||
| callables to turn them into async coroutines. For example, | |||
| could call the receiver in a thread. | |||
| :param kwargs: Extra keyword arguments to pass to each receiver. | |||
| .. versionadded:: 1.7 | |||
| """ | |||
| if self.is_muted: | |||
| return [] | |||
| results = [] | |||
| for receiver in self.receivers_for(sender): | |||
| if not iscoroutinefunction(receiver): | |||
| if _sync_wrapper is None: | |||
| raise RuntimeError("Cannot send to a non-coroutine function.") | |||
| result = await _sync_wrapper(receiver)(sender, **kwargs) | |||
| else: | |||
| result = await receiver(sender, **kwargs) | |||
| results.append((receiver, result)) | |||
| return results | |||
| def has_receivers_for(self, sender: t.Any) -> bool: | |||
| """Check if there is at least one receiver that will be called with the | |||
| given ``sender``. A receiver connected to :data:`ANY` will always be | |||
| called, regardless of sender. Does not check if weakly referenced | |||
| receivers are still live. See :meth:`receivers_for` for a stronger | |||
| search. | |||
| :param sender: Check for receivers connected to this sender, in addition | |||
| to those connected to :data:`ANY`. | |||
| """ | |||
| if not self.receivers: | |||
| return False | |||
| if self._by_sender[ANY_ID]: | |||
| return True | |||
| if sender is ANY: | |||
| return False | |||
| return make_id(sender) in self._by_sender | |||
| def receivers_for( | |||
| self, sender: t.Any | |||
| ) -> c.Generator[c.Callable[..., t.Any], None, None]: | |||
| """Yield each receiver to be called for ``sender``, in addition to those | |||
| to be called for :data:`ANY`. Weakly referenced receivers that are not | |||
| live will be disconnected and skipped. | |||
| :param sender: Yield receivers connected to this sender, in addition | |||
| to those connected to :data:`ANY`. | |||
| """ | |||
| # TODO: test receivers_for(ANY) | |||
| if not self.receivers: | |||
| return | |||
| sender_id = make_id(sender) | |||
| if sender_id in self._by_sender: | |||
| ids = self._by_sender[ANY_ID] | self._by_sender[sender_id] | |||
| else: | |||
| ids = self._by_sender[ANY_ID].copy() | |||
| for receiver_id in ids: | |||
| receiver = self.receivers.get(receiver_id) | |||
| if receiver is None: | |||
| continue | |||
| if isinstance(receiver, weakref.ref): | |||
| strong = receiver() | |||
| if strong is None: | |||
| self._disconnect(receiver_id, ANY_ID) | |||
| continue | |||
| yield strong | |||
| else: | |||
| yield receiver | |||
| def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None: | |||
| """Disconnect ``receiver`` from being called when the signal is sent by | |||
| ``sender``. | |||
| :param receiver: A connected receiver callable. | |||
| :param sender: Disconnect from only this sender. By default, disconnect | |||
| from all senders. | |||
| """ | |||
| sender_id: c.Hashable | |||
| if sender is ANY: | |||
| sender_id = ANY_ID | |||
| else: | |||
| sender_id = make_id(sender) | |||
| receiver_id = make_id(receiver) | |||
| self._disconnect(receiver_id, sender_id) | |||
| if ( | |||
| "receiver_disconnected" in self.__dict__ | |||
| and self.receiver_disconnected.receivers | |||
| ): | |||
| self.receiver_disconnected.send(self, receiver=receiver, sender=sender) | |||
| def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None: | |||
| if sender_id == ANY_ID: | |||
| if self._by_receiver.pop(receiver_id, None) is not None: | |||
| for bucket in self._by_sender.values(): | |||
| bucket.discard(receiver_id) | |||
| self.receivers.pop(receiver_id, None) | |||
| else: | |||
| self._by_sender[sender_id].discard(receiver_id) | |||
| self._by_receiver[receiver_id].discard(sender_id) | |||
| def _make_cleanup_receiver( | |||
| self, receiver_id: c.Hashable | |||
| ) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]: | |||
| """Create a callback function to disconnect a weakly referenced | |||
| receiver when it is garbage collected. | |||
| """ | |||
| def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None: | |||
| self._disconnect(receiver_id, ANY_ID) | |||
| return cleanup | |||
| def _make_cleanup_sender( | |||
| self, sender_id: c.Hashable | |||
| ) -> c.Callable[[weakref.ref[t.Any]], None]: | |||
| """Create a callback function to disconnect all receivers for a weakly | |||
| referenced sender when it is garbage collected. | |||
| """ | |||
| assert sender_id != ANY_ID | |||
| def cleanup(ref: weakref.ref[t.Any]) -> None: | |||
| self._weak_senders.pop(sender_id, None) | |||
| for receiver_id in self._by_sender.pop(sender_id, ()): | |||
| self._by_receiver[receiver_id].discard(sender_id) | |||
| return cleanup | |||
| def _cleanup_bookkeeping(self) -> None: | |||
| """Prune unused sender/receiver bookkeeping. Not threadsafe. | |||
| Connecting & disconnecting leaves behind a small amount of bookkeeping | |||
| data. Typical workloads using Blinker, for example in most web apps, | |||
| Flask, CLI scripts, etc., are not adversely affected by this | |||
| bookkeeping. | |||
| With a long-running process performing dynamic signal routing with high | |||
| volume, e.g. connecting to function closures, senders are all unique | |||
| object instances. Doing all of this over and over may cause memory usage | |||
| to grow due to extraneous bookkeeping. (An empty ``set`` for each stale | |||
| sender/receiver pair.) | |||
| This method will prune that bookkeeping away, with the caveat that such | |||
| pruning is not threadsafe. The risk is that cleanup of a fully | |||
| disconnected receiver/sender pair occurs while another thread is | |||
| connecting that same pair. If you are in the highly dynamic, unique | |||
| receiver/sender situation that has lead you to this method, that failure | |||
| mode is perhaps not a big deal for you. | |||
| """ | |||
| for mapping in (self._by_sender, self._by_receiver): | |||
| for ident, bucket in list(mapping.items()): | |||
| if not bucket: | |||
| mapping.pop(ident, None) | |||
| def _clear_state(self) -> None: | |||
| """Disconnect all receivers and senders. Useful for tests.""" | |||
| self._weak_senders.clear() | |||
| self.receivers.clear() | |||
| self._by_sender.clear() | |||
| self._by_receiver.clear() | |||
| _receiver_connected = Signal( | |||
| """\ | |||
| Sent by a :class:`Signal` after a receiver connects. | |||
| :argument: the Signal that was connected to | |||
| :keyword receiver_arg: the connected receiver | |||
| :keyword sender_arg: the sender to connect to | |||
| :keyword weak_arg: true if the connection to receiver_arg is a weak reference | |||
| .. deprecated:: 1.2 | |||
| Individual signals have their own :attr:`~Signal.receiver_connected` and | |||
| :attr:`~Signal.receiver_disconnected` signals with a slightly simplified | |||
| call signature. This global signal will be removed in Blinker 1.9. | |||
| """ | |||
| ) | |||
| class NamedSignal(Signal): | |||
| """A named generic notification emitter. The name is not used by the signal | |||
| itself, but matches the key in the :class:`Namespace` that it belongs to. | |||
| :param name: The name of the signal within the namespace. | |||
| :param doc: The docstring for the signal. | |||
| """ | |||
| def __init__(self, name: str, doc: str | None = None) -> None: | |||
| super().__init__(doc) | |||
| #: The name of this signal. | |||
| self.name: str = name | |||
| def __repr__(self) -> str: | |||
| base = super().__repr__() | |||
| return f"{base[:-1]}; {self.name!r}>" # noqa: E702 | |||
| if t.TYPE_CHECKING: | |||
| class PNamespaceSignal(t.Protocol): | |||
| def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ... | |||
| # Python < 3.9 | |||
| _NamespaceBase = dict[str, NamedSignal] # type: ignore[misc] | |||
| else: | |||
| _NamespaceBase = dict | |||
| class Namespace(_NamespaceBase): | |||
| """A dict mapping names to signals.""" | |||
| def signal(self, name: str, doc: str | None = None) -> NamedSignal: | |||
| """Return the :class:`NamedSignal` for the given ``name``, creating it | |||
| if required. Repeated calls with the same name return the same signal. | |||
| :param name: The name of the signal. | |||
| :param doc: The docstring of the signal. | |||
| """ | |||
| if name not in self: | |||
| self[name] = NamedSignal(name, doc) | |||
| return self[name] | |||
| class _WeakNamespace(WeakValueDictionary): # type: ignore[type-arg] | |||
| """A weak mapping of names to signals. | |||
| Automatically cleans up unused signals when the last reference goes out | |||
| of scope. This namespace implementation provides similar behavior to Blinker | |||
| <= 1.2. | |||
| .. deprecated:: 1.3 | |||
| Will be removed in Blinker 1.9. | |||
| .. versionadded:: 1.3 | |||
| """ | |||
| def __init__(self) -> None: | |||
| warnings.warn( | |||
| "'WeakNamespace' is deprecated and will be removed in Blinker 1.9." | |||
| " Use 'Namespace' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| super().__init__() | |||
| def signal(self, name: str, doc: str | None = None) -> NamedSignal: | |||
| """Return the :class:`NamedSignal` for the given ``name``, creating it | |||
| if required. Repeated calls with the same name return the same signal. | |||
| :param name: The name of the signal. | |||
| :param doc: The docstring of the signal. | |||
| """ | |||
| if name not in self: | |||
| self[name] = NamedSignal(name, doc) | |||
| return self[name] # type: ignore[no-any-return] | |||
| default_namespace: Namespace = Namespace() | |||
| """A default :class:`Namespace` for creating named signals. :func:`signal` | |||
| creates a :class:`NamedSignal` in this namespace. | |||
| """ | |||
| signal: PNamespaceSignal = default_namespace.signal | |||
| """Return a :class:`NamedSignal` in :data:`default_namespace` with the given | |||
| ``name``, creating it if required. Repeated calls with the same name return the | |||
| same signal. | |||
| """ | |||
| def __getattr__(name: str) -> t.Any: | |||
| if name == "receiver_connected": | |||
| warnings.warn( | |||
| "The global 'receiver_connected' signal is deprecated and will be" | |||
| " removed in Blinker 1.9. Use 'Signal.receiver_connected' and" | |||
| " 'Signal.receiver_disconnected' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return _receiver_connected | |||
| if name == "WeakNamespace": | |||
| warnings.warn( | |||
| "'WeakNamespace' is deprecated and will be removed in Blinker 1.9." | |||
| " Use 'Namespace' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return _WeakNamespace | |||
| raise AttributeError(name) | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,28 @@ | |||
| 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. | |||
| @ -0,0 +1,103 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: click | |||
| Version: 8.1.7 | |||
| Summary: Composable command line interface toolkit | |||
| Home-page: https://palletsprojects.com/p/click/ | |||
| Maintainer: Pallets | |||
| Maintainer-email: contact@palletsprojects.com | |||
| License: BSD-3-Clause | |||
| Project-URL: Donate, https://palletsprojects.com/donate | |||
| Project-URL: Documentation, https://click.palletsprojects.com/ | |||
| Project-URL: Changes, https://click.palletsprojects.com/changes/ | |||
| Project-URL: Source Code, https://github.com/pallets/click/ | |||
| Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ | |||
| Project-URL: Chat, https://discord.gg/pallets | |||
| Classifier: Development Status :: 5 - Production/Stable | |||
| Classifier: Intended Audience :: Developers | |||
| Classifier: License :: OSI Approved :: BSD License | |||
| Classifier: Operating System :: OS Independent | |||
| Classifier: Programming Language :: Python | |||
| Requires-Python: >=3.7 | |||
| Description-Content-Type: text/x-rst | |||
| License-File: LICENSE.rst | |||
| Requires-Dist: colorama ; platform_system == "Windows" | |||
| Requires-Dist: importlib-metadata ; python_version < "3.8" | |||
| \$ 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 | |||
| Installing | |||
| ---------- | |||
| Install and update using `pip`_: | |||
| .. code-block:: text | |||
| $ pip install -U click | |||
| .. _pip: https://pip.pypa.io/en/stable/getting-started/ | |||
| A Simple Example | |||
| ---------------- | |||
| .. code-block:: 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() | |||
| .. code-block:: text | |||
| $ 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 | |||
| Links | |||
| ----- | |||
| - Documentation: https://click.palletsprojects.com/ | |||
| - Changes: https://click.palletsprojects.com/changes/ | |||
| - PyPI Releases: https://pypi.org/project/click/ | |||
| - Source Code: https://github.com/pallets/click | |||
| - Issue Tracker: https://github.com/pallets/click/issues | |||
| - Chat: https://discord.gg/pallets | |||
| @ -0,0 +1,39 @@ | |||
| click-8.1.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| click-8.1.7.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 | |||
| click-8.1.7.dist-info/METADATA,sha256=qIMevCxGA9yEmJOM_4WHuUJCwWpsIEVbCPOhs45YPN4,3014 | |||
| click-8.1.7.dist-info/RECORD,, | |||
| click-8.1.7.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92 | |||
| click-8.1.7.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 | |||
| click/__init__.py,sha256=YDDbjm406dTOA0V8bTtdGnhN7zj5j-_dFRewZF_pLvw,3138 | |||
| click/__pycache__/__init__.cpython-310.pyc,, | |||
| click/__pycache__/_compat.cpython-310.pyc,, | |||
| click/__pycache__/_termui_impl.cpython-310.pyc,, | |||
| click/__pycache__/_textwrap.cpython-310.pyc,, | |||
| click/__pycache__/_winconsole.cpython-310.pyc,, | |||
| click/__pycache__/core.cpython-310.pyc,, | |||
| click/__pycache__/decorators.cpython-310.pyc,, | |||
| click/__pycache__/exceptions.cpython-310.pyc,, | |||
| click/__pycache__/formatting.cpython-310.pyc,, | |||
| click/__pycache__/globals.cpython-310.pyc,, | |||
| click/__pycache__/parser.cpython-310.pyc,, | |||
| click/__pycache__/shell_completion.cpython-310.pyc,, | |||
| click/__pycache__/termui.cpython-310.pyc,, | |||
| click/__pycache__/testing.cpython-310.pyc,, | |||
| click/__pycache__/types.cpython-310.pyc,, | |||
| click/__pycache__/utils.cpython-310.pyc,, | |||
| click/_compat.py,sha256=5318agQpbt4kroKsbqDOYpTSWzL_YCZVUQiTT04yXmc,18744 | |||
| click/_termui_impl.py,sha256=3dFYv4445Nw-rFvZOTBMBPYwB1bxnmNk9Du6Dm_oBSU,24069 | |||
| click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353 | |||
| click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860 | |||
| click/core.py,sha256=j6oEWtGgGna8JarD6WxhXmNnxLnfRjwXglbBc-8jr7U,114086 | |||
| click/decorators.py,sha256=-ZlbGYgV-oI8jr_oH4RpuL1PFS-5QmeuEAsLDAYgxtw,18719 | |||
| click/exceptions.py,sha256=fyROO-47HWFDjt2qupo7A3J32VlpM-ovJnfowu92K3s,9273 | |||
| click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706 | |||
| click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961 | |||
| click/parser.py,sha256=LKyYQE9ZLj5KgIDXkrcTHQRXIggfoivX14_UVIn56YA,19067 | |||
| click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| click/shell_completion.py,sha256=Ty3VM_ts0sQhj6u7eFTiLwHPoTgcXTGEAUg2OpLqYKw,18460 | |||
| click/termui.py,sha256=H7Q8FpmPelhJ2ovOhfCRhjMtCpNyjFXryAMLZODqsdc,28324 | |||
| click/testing.py,sha256=1Qd4kS5bucn1hsNIRryd0WtTMuCpkA93grkWxT8POsU,16084 | |||
| click/types.py,sha256=TZvz3hKvBztf-Hpa2enOmP4eznSPLzijjig5b_0XMxE,36391 | |||
| click/utils.py,sha256=1476UduUNY6UePGU4m18uzVHLt1sKM2PP3yWsQhbItM,20298 | |||
| @ -0,0 +1,5 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: bdist_wheel (0.41.1) | |||
| Root-Is-Purelib: true | |||
| Tag: py3-none-any | |||
| @ -0,0 +1 @@ | |||
| click | |||
| @ -0,0 +1,73 @@ | |||
| """ | |||
| 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 .core import Argument as Argument | |||
| from .core import BaseCommand as BaseCommand | |||
| 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 MultiCommand as MultiCommand | |||
| 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 .parser import OptionParser as OptionParser | |||
| 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 | |||
| __version__ = "8.1.7" | |||
| @ -0,0 +1,623 @@ | |||
| import codecs | |||
| import io | |||
| import os | |||
| import re | |||
| import sys | |||
| import typing as t | |||
| from weakref import WeakKeyDictionary | |||
| CYGWIN = sys.platform.startswith("cygwin") | |||
| WIN = sys.platform.startswith("win") | |||
| auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None | |||
| _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") | |||
| def _make_text_stream( | |||
| stream: t.BinaryIO, | |||
| encoding: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| 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: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| 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("") # type: ignore | |||
| 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.Optional[t.BinaryIO]: | |||
| # 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.Optional[t.BinaryIO]: | |||
| # 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: t.Optional[str]) -> 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: t.Optional[str], errors: t.Optional[str] | |||
| ) -> 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: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| is_binary: t.Callable[[t.IO[t.Any], bool], bool], | |||
| find_binary: t.Callable[[t.IO[t.Any]], t.Optional[t.BinaryIO]], | |||
| 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: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| 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: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| 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: t.Optional[str] = None, errors: t.Optional[str] = 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: t.Optional[str] = None, errors: t.Optional[str] = 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: t.Optional[str] = None, errors: t.Optional[str] = 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: t.Union[str, "os.PathLike[str]", int], | |||
| mode: str, | |||
| encoding: t.Optional[str], | |||
| errors: t.Optional[str], | |||
| ) -> 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: "t.Union[str, os.PathLike[str]]", | |||
| mode: str = "r", | |||
| encoding: t.Optional[str] = None, | |||
| errors: t.Optional[str] = "strict", | |||
| atomic: bool = False, | |||
| ) -> t.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: t.Optional[int] = 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: t.Optional[t.Type[BaseException]], *_: t.Any) -> 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.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = 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: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() | |||
| def auto_wrap_for_ansi( # noqa: F811 | |||
| stream: t.TextIO, color: t.Optional[bool] = 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): | |||
| try: | |||
| return _write(s) | |||
| except BaseException: | |||
| ansi_wrapper.reset_all() | |||
| raise | |||
| rv.write = _safe_write | |||
| 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: t.Optional[str], errors: t.Optional[str] | |||
| ) -> t.Optional[t.TextIO]: | |||
| 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.Optional[t.TextIO]], | |||
| wrapper_func: t.Callable[[], t.TextIO], | |||
| ) -> t.Callable[[], t.Optional[t.TextIO]]: | |||
| cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() | |||
| def func() -> t.Optional[t.TextIO]: | |||
| 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: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { | |||
| "stdin": get_binary_stdin, | |||
| "stdout": get_binary_stdout, | |||
| "stderr": get_binary_stderr, | |||
| } | |||
| text_streams: t.Mapping[ | |||
| str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] | |||
| ] = { | |||
| "stdin": get_text_stdin, | |||
| "stdout": get_text_stdout, | |||
| "stderr": get_text_stderr, | |||
| } | |||
| @ -0,0 +1,739 @@ | |||
| """ | |||
| 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. | |||
| """ | |||
| import contextlib | |||
| import math | |||
| import os | |||
| import sys | |||
| import time | |||
| import typing as t | |||
| from gettext import gettext as _ | |||
| from io import StringIO | |||
| 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: t.Optional[t.Iterable[V]], | |||
| length: t.Optional[int] = None, | |||
| fill_char: str = "#", | |||
| empty_char: str = " ", | |||
| bar_template: str = "%(bar)s", | |||
| info_sep: str = " ", | |||
| show_eta: bool = True, | |||
| show_percent: t.Optional[bool] = None, | |||
| show_pos: bool = False, | |||
| item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, | |||
| label: t.Optional[str] = None, | |||
| file: t.Optional[t.TextIO] = None, | |||
| color: t.Optional[bool] = 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.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(t.Iterable[V], range(length)) | |||
| self.iter: t.Iterable[V] = iter(iterable) | |||
| self.length = length | |||
| self.pos = 0 | |||
| self.avg: t.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: t.Optional[int] = None | |||
| self.entered: bool = False | |||
| self.current_item: t.Optional[V] = None | |||
| self.is_hidden: bool = not isatty(self.file) | |||
| self._last_line: t.Optional[str] = None | |||
| def __enter__(self) -> "ProgressBar[V]": | |||
| self.entered = True | |||
| self.render_progress() | |||
| return self | |||
| def __exit__( | |||
| self, | |||
| exc_type: t.Optional[t.Type[BaseException]], | |||
| exc_value: t.Optional[BaseException], | |||
| tb: t.Optional[TracebackType], | |||
| ) -> None: | |||
| self.render_finish() | |||
| def __iter__(self) -> t.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.is_hidden: | |||
| 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: | |||
| import shutil | |||
| if self.is_hidden: | |||
| # Only output the label as it changes if the output is not a | |||
| # TTY. Use file=stderr if you expect to be piping stdout. | |||
| 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: | |||
| 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: | |||
| buf.append(BEFORE_BAR) | |||
| buf.append(" " * self.max_width) # type: ignore | |||
| 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: t.Optional[V] = 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) -> t.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 self.is_hidden: | |||
| 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: t.Iterable[str], color: t.Optional[bool] = 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) | |||
| pager_cmd = (os.environ.get("PAGER", None) or "").strip() | |||
| if pager_cmd: | |||
| if WIN: | |||
| return _tempfilepager(generator, pager_cmd, color) | |||
| return _pipepager(generator, pager_cmd, color) | |||
| if os.environ.get("TERM") in ("dumb", "emacs"): | |||
| return _nullpager(stdout, generator, color) | |||
| if WIN or sys.platform.startswith("os2"): | |||
| return _tempfilepager(generator, "more <", color) | |||
| if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: | |||
| return _pipepager(generator, "less", color) | |||
| import tempfile | |||
| fd, filename = tempfile.mkstemp() | |||
| os.close(fd) | |||
| try: | |||
| if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: | |||
| return _pipepager(generator, "more", color) | |||
| return _nullpager(stdout, generator, color) | |||
| finally: | |||
| os.unlink(filename) | |||
| def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: | |||
| """Page through text by feeding it to another program. Invoking a | |||
| pager through this might support colors. | |||
| """ | |||
| import subprocess | |||
| env = dict(os.environ) | |||
| # If we're piping to less we might support colors under the | |||
| # condition that | |||
| cmd_detail = cmd.rsplit("/", 1)[-1].split() | |||
| if color is None and cmd_detail[0] == "less": | |||
| less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" | |||
| if not less_flags: | |||
| env["LESS"] = "-R" | |||
| color = True | |||
| elif "r" in less_flags or "R" in less_flags: | |||
| color = True | |||
| c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) | |||
| stdin = t.cast(t.BinaryIO, c.stdin) | |||
| encoding = get_best_encoding(stdin) | |||
| try: | |||
| for text in generator: | |||
| if not color: | |||
| text = strip_ansi(text) | |||
| stdin.write(text.encode(encoding, "replace")) | |||
| except (OSError, KeyboardInterrupt): | |||
| pass | |||
| else: | |||
| stdin.close() | |||
| # 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 | |||
| def _tempfilepager( | |||
| generator: t.Iterable[str], cmd: str, color: t.Optional[bool] | |||
| ) -> None: | |||
| """Page through text by invoking a program on a temporary file.""" | |||
| 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: | |||
| os.system(f'{cmd} "{filename}"') | |||
| finally: | |||
| os.close(fd) | |||
| os.unlink(filename) | |||
| def _nullpager( | |||
| stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] | |||
| ) -> 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: t.Optional[str] = None, | |||
| env: t.Optional[t.Mapping[str, str]] = 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" | |||
| for editor in "sensible-editor", "vim", "nano": | |||
| if os.system(f"which {editor} >/dev/null 2>&1") == 0: | |||
| return editor | |||
| return "vi" | |||
| def edit_file(self, filename: str) -> None: | |||
| import subprocess | |||
| editor = self.get_editor() | |||
| environ: t.Optional[t.Dict[str, str]] = None | |||
| if self.env: | |||
| environ = os.environ.copy() | |||
| environ.update(self.env) | |||
| try: | |||
| c = subprocess.Popen(f'{editor} "{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 | |||
| def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: | |||
| import tempfile | |||
| if not text: | |||
| data = 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_file(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") # type: ignore | |||
| 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.replace('"', "")) | |||
| args = f'explorer /select,"{url}"' | |||
| else: | |||
| url = url.replace('"', "") | |||
| wait_str = "/WAIT" if wait else "" | |||
| args = f'start {wait_str} "" "{url}"' | |||
| return os.system(args) | |||
| elif CYGWIN: | |||
| if locate: | |||
| url = os.path.dirname(_unquote_file(url).replace('"', "")) | |||
| args = f'cygstart "{url}"' | |||
| else: | |||
| url = url.replace('"', "") | |||
| wait_str = "-w" if wait else "" | |||
| args = f'cygstart {wait_str} "{url}"' | |||
| return os.system(args) | |||
| 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) -> t.Optional[BaseException]: | |||
| 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 WIN: | |||
| import msvcrt | |||
| @contextlib.contextmanager | |||
| def raw_terminal() -> t.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`. | |||
| func: t.Callable[[], str] | |||
| if echo: | |||
| func = msvcrt.getwche # type: ignore | |||
| else: | |||
| func = msvcrt.getwch # type: ignore | |||
| 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 tty | |||
| import termios | |||
| @contextlib.contextmanager | |||
| def raw_terminal() -> t.Iterator[int]: | |||
| f: t.Optional[t.TextIO] | |||
| 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 | |||
| @ -0,0 +1,49 @@ | |||
| import textwrap | |||
| import typing as t | |||
| from contextlib import contextmanager | |||
| class TextWrapper(textwrap.TextWrapper): | |||
| def _handle_long_word( | |||
| self, | |||
| reversed_chunks: t.List[str], | |||
| cur_line: t.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) -> t.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) | |||
| @ -0,0 +1,279 @@ | |||
| # 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. | |||
| import io | |||
| import sys | |||
| import time | |||
| import typing as t | |||
| 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 | |||
| 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_ = [ | |||
| ("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, writable=False): | |||
| buf = Py_buffer() | |||
| flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE | |||
| PyObject_GetBuffer(py_object(obj), byref(buf), flags) | |||
| try: | |||
| buffer_type = c_char * buf.len | |||
| return buffer_type.from_address(buf.buf) | |||
| finally: | |||
| PyBuffer_Release(byref(buf)) | |||
| class _WindowsConsoleRawIOBase(io.RawIOBase): | |||
| def __init__(self, handle): | |||
| self.handle = handle | |||
| def isatty(self): | |||
| super().isatty() | |||
| return True | |||
| class _WindowsConsoleReader(_WindowsConsoleRawIOBase): | |||
| def readable(self): | |||
| return True | |||
| def readinto(self, b): | |||
| 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): | |||
| return True | |||
| @staticmethod | |||
| def _get_error_message(errno): | |||
| 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): | |||
| 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: t.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): | |||
| return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>" | |||
| 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: t.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: t.Optional[str], errors: t.Optional[str] | |||
| ) -> t.Optional[t.TextIO]: | |||
| if ( | |||
| get_buffer is not None | |||
| and encoding in {"utf-16-le", None} | |||
| and errors in {"strict", None} | |||
| and _is_console(f) | |||
| ): | |||
| func = _stream_factories.get(f.fileno()) | |||
| if func is not None: | |||
| b = getattr(f, "buffer", None) | |||
| if b is None: | |||
| return None | |||
| return func(b) | |||
| @ -0,0 +1,561 @@ | |||
| import inspect | |||
| import types | |||
| 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=t.Union[_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.Any, 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: t.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.Optional[T] | |||
| 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 # type: ignore[return-value] | |||
| def pass_meta_key( | |||
| key: str, *, doc_description: t.Optional[str] = None | |||
| ) -> "t.Callable[[t.Callable[te.Concatenate[t.Any, 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.Any, 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 # type: ignore[return-value] | |||
| 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: t.Optional[str], | |||
| cls: t.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: t.Type[CmdType], | |||
| **attrs: t.Any, | |||
| ) -> t.Callable[[_AnyCallable], CmdType]: | |||
| ... | |||
| # variant: with optional string name, no cls argument provided. | |||
| @t.overload | |||
| def command( | |||
| name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any | |||
| ) -> t.Callable[[_AnyCallable], Command]: | |||
| ... | |||
| def command( | |||
| name: t.Union[t.Optional[str], _AnyCallable] = None, | |||
| cls: t.Optional[t.Type[CmdType]] = None, | |||
| **attrs: t.Any, | |||
| ) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[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 with | |||
| underscores replaced by dashes. If you want to change that, you can | |||
| pass the intended name as the first argument. | |||
| 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. This defaults to the function | |||
| name with underscores replaced by dashes. | |||
| :param cls: the command class to instantiate. This defaults to | |||
| :class:`Command`. | |||
| .. 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.Optional[t.Callable[[_AnyCallable], t.Any]] = 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(t.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) | |||
| cmd = cls( | |||
| name=name or f.__name__.lower().replace("_", "-"), | |||
| 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: t.Optional[str], | |||
| cls: t.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: t.Type[GrpType], | |||
| **attrs: t.Any, | |||
| ) -> t.Callable[[_AnyCallable], GrpType]: | |||
| ... | |||
| # variant: with optional string name, no cls argument provided. | |||
| @t.overload | |||
| def group( | |||
| name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any | |||
| ) -> t.Callable[[_AnyCallable], Group]: | |||
| ... | |||
| def group( | |||
| name: t.Union[str, _AnyCallable, None] = None, | |||
| cls: t.Optional[t.Type[GrpType]] = None, | |||
| **attrs: t.Any, | |||
| ) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[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(t.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: t.Optional[t.Type[Argument]] = 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: t.Optional[t.Type[Option]] = 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: t.Optional[str] = None, | |||
| *param_decls: str, | |||
| package_name: t.Optional[str] = None, | |||
| prog_name: t.Optional[str] = None, | |||
| message: t.Optional[str] = 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``. On Python < 3.8, the ``importlib_metadata`` | |||
| backport must be installed. | |||
| 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: | |||
| metadata: t.Optional[types.ModuleType] | |||
| try: | |||
| from importlib import metadata # type: ignore | |||
| except ImportError: | |||
| # Python < 3.8 | |||
| import importlib_metadata as metadata # type: ignore | |||
| try: | |||
| version = metadata.version(package_name) # type: ignore | |||
| except metadata.PackageNotFoundError: # type: ignore | |||
| 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]: | |||
| """Add a ``--help`` option which immediately prints the help page | |||
| and exits the program. | |||
| This is usually unnecessary, as the ``--help`` option is added to | |||
| each command automatically unless ``add_help_option=False`` is | |||
| passed. | |||
| :param param_decls: One or more option names. Defaults to the single | |||
| value ``"--help"``. | |||
| :param kwargs: Extra arguments are passed to :func:`option`. | |||
| """ | |||
| def callback(ctx: Context, param: Parameter, value: bool) -> None: | |||
| if not value or ctx.resilient_parsing: | |||
| return | |||
| 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["callback"] = callback | |||
| return option(*param_decls, **kwargs) | |||
| @ -0,0 +1,288 @@ | |||
| import typing as t | |||
| from gettext import gettext as _ | |||
| from gettext import ngettext | |||
| from ._compat import get_text_stderr | |||
| 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: t.Optional[t.Union[t.Sequence[str], str]] | |||
| ) -> t.Optional[str]: | |||
| 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) | |||
| self.message = message | |||
| def format_message(self) -> str: | |||
| return self.message | |||
| def __str__(self) -> str: | |||
| return self.message | |||
| def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: | |||
| if file is None: | |||
| file = get_text_stderr() | |||
| echo(_("Error: {message}").format(message=self.format_message()), file=file) | |||
| 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: t.Optional["Context"] = None) -> None: | |||
| super().__init__(message) | |||
| self.ctx = ctx | |||
| self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None | |||
| def show(self, file: t.Optional[t.IO[t.Any]] = 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: t.Optional["Context"] = None, | |||
| param: t.Optional["Parameter"] = None, | |||
| param_hint: t.Optional[str] = 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: t.Optional[str] = None, | |||
| ctx: t.Optional["Context"] = None, | |||
| param: t.Optional["Parameter"] = None, | |||
| param_hint: t.Optional[str] = None, | |||
| param_type: t.Optional[str] = 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: t.Optional[str] = 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(self.param) | |||
| 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: t.Optional[str] = None, | |||
| possibilities: t.Optional[t.Sequence[str]] = None, | |||
| ctx: t.Optional["Context"] = 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: t.Optional["Context"] = 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 FileError(ClickException): | |||
| """Raised if a file cannot be opened.""" | |||
| def __init__(self, filename: str, hint: t.Optional[str] = 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 | |||
| @ -0,0 +1,301 @@ | |||
| import typing as t | |||
| 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: t.Optional[int] = None | |||
| def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: | |||
| widths: t.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: t.Iterable[t.Tuple[str, str]], col_count: int | |||
| ) -> t.Iterator[t.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: t.List[t.Tuple[int, bool, str]] = [] | |||
| buf: t.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: t.Optional[int] = None, | |||
| max_width: t.Optional[int] = None, | |||
| ) -> None: | |||
| import shutil | |||
| self.indent_increment = indent_increment | |||
| if max_width is None: | |||
| max_width = 80 | |||
| if width is None: | |||
| 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 = 0 | |||
| self.buffer: t.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: t.Optional[str] = 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: t.Sequence[t.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) -> t.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) -> t.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: t.Sequence[str]) -> t.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 | |||
| @ -0,0 +1,68 @@ | |||
| import typing as t | |||
| from threading import local | |||
| if t.TYPE_CHECKING: | |||
| import typing_extensions as te | |||
| from .core import Context | |||
| _local = local() | |||
| @t.overload | |||
| def get_current_context(silent: "te.Literal[False]" = False) -> "Context": | |||
| ... | |||
| @t.overload | |||
| def get_current_context(silent: bool = ...) -> t.Optional["Context"]: | |||
| ... | |||
| def get_current_context(silent: bool = False) -> t.Optional["Context"]: | |||
| """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: t.Optional[bool] = None) -> t.Optional[bool]: | |||
| """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 | |||
| @ -0,0 +1,529 @@ | |||
| """ | |||
| 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 | |||
| import typing as t | |||
| from collections import deque | |||
| from gettext import gettext as _ | |||
| from gettext import ngettext | |||
| from .exceptions import BadArgumentUsage | |||
| from .exceptions import BadOptionUsage | |||
| from .exceptions import NoSuchOption | |||
| from .exceptions import UsageError | |||
| if t.TYPE_CHECKING: | |||
| import typing_extensions as te | |||
| 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") | |||
| # Sentinel value that indicates 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. | |||
| _flag_needs_value = object() | |||
| def _unpack_args( | |||
| args: t.Sequence[str], nargs_spec: t.Sequence[int] | |||
| ) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.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 `None`. | |||
| """ | |||
| args = deque(args) | |||
| nargs_spec = deque(nargs_spec) | |||
| rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] | |||
| spos: t.Optional[int] = None | |||
| def _fetch(c: "te.Deque[V]") -> t.Optional[V]: | |||
| try: | |||
| if spos is None: | |||
| return c.popleft() | |||
| else: | |||
| return c.pop() | |||
| except IndexError: | |||
| return None | |||
| while nargs_spec: | |||
| nargs = _fetch(nargs_spec) | |||
| if nargs is None: | |||
| continue | |||
| if nargs == 1: | |||
| rv.append(_fetch(args)) | |||
| 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(None) | |||
| # 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) -> t.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: t.Optional["Context"]) -> 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)}" | |||
| def split_arg_string(string: str) -> t.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. | |||
| """ | |||
| 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 | |||
| class Option: | |||
| def __init__( | |||
| self, | |||
| obj: "CoreOption", | |||
| opts: t.Sequence[str], | |||
| dest: t.Optional[str], | |||
| action: t.Optional[str] = None, | |||
| nargs: int = 1, | |||
| const: t.Optional[t.Any] = None, | |||
| ): | |||
| self._short_opts = [] | |||
| self._long_opts = [] | |||
| self.prefixes: t.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: t.Optional[str], nargs: int = 1): | |||
| self.dest = dest | |||
| self.nargs = nargs | |||
| self.obj = obj | |||
| def process( | |||
| self, | |||
| value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], | |||
| state: "ParsingState", | |||
| ) -> None: | |||
| if self.nargs > 1: | |||
| assert value is not None | |||
| holes = sum(1 for x in value if x is None) | |||
| if holes == len(value): | |||
| value = None | |||
| elif holes != 0: | |||
| raise BadArgumentUsage( | |||
| _("Argument {name!r} takes {nargs} values.").format( | |||
| name=self.dest, nargs=self.nargs | |||
| ) | |||
| ) | |||
| if self.nargs == -1 and self.obj.envvar is not None and value == (): | |||
| # Replace empty tuple with None so that a value from the | |||
| # environment may be tried. | |||
| value = None | |||
| state.opts[self.dest] = value # type: ignore | |||
| state.order.append(self.obj) | |||
| class ParsingState: | |||
| def __init__(self, rargs: t.List[str]) -> None: | |||
| self.opts: t.Dict[str, t.Any] = {} | |||
| self.largs: t.List[str] = [] | |||
| self.rargs = rargs | |||
| self.order: t.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. | |||
| """ | |||
| def __init__(self, ctx: t.Optional["Context"] = 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: t.Dict[str, Option] = {} | |||
| self._long_opt: t.Dict[str, Option] = {} | |||
| self._opt_prefixes = {"-", "--"} | |||
| self._args: t.List[Argument] = [] | |||
| def add_option( | |||
| self, | |||
| obj: "CoreOption", | |||
| opts: t.Sequence[str], | |||
| dest: t.Optional[str], | |||
| action: t.Optional[str] = None, | |||
| nargs: int = 1, | |||
| const: t.Optional[t.Any] = 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: t.Optional[str], 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: t.List[str] | |||
| ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.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: t.Optional[str], 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 = None | |||
| 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 = None | |||
| 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 | |||
| ) -> t.Any: | |||
| nargs = option.nargs | |||
| 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) | |||
| @ -0,0 +1,596 @@ | |||
| import os | |||
| import re | |||
| import typing as t | |||
| from gettext import gettext as _ | |||
| from .core import Argument | |||
| from .core import BaseCommand | |||
| from .core import Context | |||
| from .core import MultiCommand | |||
| from .core import Option | |||
| from .core import Parameter | |||
| from .core import ParameterSource | |||
| from .parser import split_arg_string | |||
| from .utils import echo | |||
| def shell_complete( | |||
| cli: BaseCommand, | |||
| ctx_args: t.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: t.Optional[str] = None, | |||
| **kwargs: t.Any, | |||
| ) -> None: | |||
| self.value: t.Any = value | |||
| self.type: str = type | |||
| self.help: t.Optional[str] = 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; | |||
| """ | |||
| _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: BaseCommand, | |||
| ctx_args: t.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) -> t.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) -> t.Tuple[t.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: t.List[str], incomplete: str | |||
| ) -> t.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 subprocess | |||
| output = subprocess.run( | |||
| ["bash", "-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) -> t.Tuple[t.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) -> t.Tuple[t.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}\n{item.value}\n{item.help if item.help else '_'}" | |||
| class FishComplete(ShellComplete): | |||
| """Shell completion for Fish.""" | |||
| name = "fish" | |||
| source_template = _SOURCE_FISH | |||
| def get_completion_args(self) -> t.Tuple[t.List[str], str]: | |||
| cwords = split_arg_string(os.environ["COMP_WORDS"]) | |||
| incomplete = os.environ["COMP_CWORD"] | |||
| 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=t.Type[ShellComplete]) | |||
| _available_shells: t.Dict[str, t.Type[ShellComplete]] = { | |||
| "bash": BashComplete, | |||
| "fish": FishComplete, | |||
| "zsh": ZshComplete, | |||
| } | |||
| def add_completion_class( | |||
| cls: ShellCompleteType, name: t.Optional[str] = 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) -> t.Optional[t.Type[ShellComplete]]: | |||
| """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 _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: t.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 | |||
| return last_option is not None and last_option in param.opts | |||
| def _resolve_context( | |||
| cli: BaseCommand, | |||
| ctx_args: t.MutableMapping[str, t.Any], | |||
| prog_name: str, | |||
| args: t.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 | |||
| ctx = cli.make_context(prog_name, args.copy(), **ctx_args) | |||
| args = ctx.protected_args + ctx.args | |||
| while args: | |||
| command = ctx.command | |||
| if isinstance(command, MultiCommand): | |||
| if not command.chain: | |||
| name, cmd, args = command.resolve_command(ctx, args) | |||
| if cmd is None: | |||
| return ctx | |||
| ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) | |||
| 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 | |||
| sub_ctx = cmd.make_context( | |||
| name, | |||
| args, | |||
| parent=ctx, | |||
| allow_extra_args=True, | |||
| allow_interspersed_args=False, | |||
| resilient_parsing=True, | |||
| ) | |||
| 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: t.List[str], incomplete: str | |||
| ) -> t.Tuple[t.Union[BaseCommand, 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 | |||
| @ -0,0 +1,784 @@ | |||
| import inspect | |||
| import io | |||
| import itertools | |||
| import sys | |||
| import typing as t | |||
| 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.Optional[t.Any] = None, | |||
| show_choices: bool = True, | |||
| type: t.Optional[ParamType] = 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.Optional[t.Any] = None, | |||
| hide_input: bool = False, | |||
| confirmation_prompt: t.Union[bool, str] = False, | |||
| type: t.Optional[t.Union[ParamType, t.Any]] = None, | |||
| value_proc: t.Optional[t.Callable[[str], t.Any]] = 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): ". | |||
| .. 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.rstrip(" "), nl=False, err=err) | |||
| # Echo a space to stdout to work around an issue where | |||
| # readline causes backspace to clear the whole line. | |||
| return f(" ") | |||
| 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) # noqa: B306 | |||
| 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: t.Optional[bool] = 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.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.rstrip(" "), nl=False, err=err) | |||
| # Echo a space to stdout to work around an issue where | |||
| # readline causes backspace to clear the whole line. | |||
| value = visible_prompt_func(" ").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: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], | |||
| color: t.Optional[bool] = 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[[], t.Iterable[str]], text_or_generator)() | |||
| elif isinstance(text_or_generator, str): | |||
| i = [text_or_generator] | |||
| else: | |||
| i = iter(t.cast(t.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) | |||
| def progressbar( | |||
| iterable: t.Optional[t.Iterable[V]] = None, | |||
| length: t.Optional[int] = None, | |||
| label: t.Optional[str] = None, | |||
| show_eta: bool = True, | |||
| show_percent: t.Optional[bool] = None, | |||
| show_pos: bool = False, | |||
| item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, | |||
| fill_char: str = "#", | |||
| empty_char: str = "-", | |||
| bar_template: str = "%(label)s [%(bar)s] %(info)s", | |||
| info_sep: str = " ", | |||
| width: int = 36, | |||
| file: t.Optional[t.TextIO] = None, | |||
| color: t.Optional[bool] = 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 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. | |||
| .. 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 | |||
| Added the ``update_min_steps`` parameter. | |||
| .. versionchanged:: 4.0 | |||
| Added the ``color`` parameter. Added the ``update`` method to | |||
| the object. | |||
| .. versionadded:: 2.0 | |||
| """ | |||
| from ._termui_impl import ProgressBar | |||
| color = resolve_color_default(color) | |||
| return ProgressBar( | |||
| iterable=iterable, | |||
| length=length, | |||
| 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: t.Union[int, t.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: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, | |||
| bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, | |||
| bold: t.Optional[bool] = None, | |||
| dim: t.Optional[bool] = None, | |||
| underline: t.Optional[bool] = None, | |||
| overline: t.Optional[bool] = None, | |||
| italic: t.Optional[bool] = None, | |||
| blink: t.Optional[bool] = None, | |||
| reverse: t.Optional[bool] = None, | |||
| strikethrough: t.Optional[bool] = 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.Optional[t.Any] = None, | |||
| file: t.Optional[t.IO[t.AnyStr]] = None, | |||
| nl: bool = True, | |||
| err: bool = False, | |||
| color: t.Optional[bool] = 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) | |||
| def edit( | |||
| text: t.Optional[t.AnyStr] = None, | |||
| editor: t.Optional[str] = None, | |||
| env: t.Optional[t.Mapping[str, str]] = None, | |||
| require_save: bool = True, | |||
| extension: str = ".txt", | |||
| filename: t.Optional[str] = None, | |||
| ) -> t.Optional[t.AnyStr]: | |||
| 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. | |||
| """ | |||
| 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) | |||
| ed.edit_file(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.Optional[t.Callable[[bool], str]] = 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() -> t.ContextManager[int]: | |||
| from ._termui_impl import raw_terminal as f | |||
| return f() | |||
| def pause(info: t.Optional[str] = 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) | |||
| @ -0,0 +1,479 @@ | |||
| import contextlib | |||
| import io | |||
| import os | |||
| import shlex | |||
| import shutil | |||
| import sys | |||
| import tempfile | |||
| import typing as t | |||
| from types import TracebackType | |||
| from . import formatting | |||
| from . import termui | |||
| from . import utils | |||
| from ._compat import _find_binary_reader | |||
| if t.TYPE_CHECKING: | |||
| from .core import BaseCommand | |||
| 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) -> t.List[bytes]: | |||
| return [self._echo(x) for x in self._input.readlines()] | |||
| def __iter__(self) -> t.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: t.Optional[EchoingStdin]) -> t.Iterator[None]: | |||
| if stream is None: | |||
| yield | |||
| else: | |||
| stream._paused = True | |||
| yield | |||
| stream._paused = False | |||
| 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: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], 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.""" | |||
| def __init__( | |||
| self, | |||
| runner: "CliRunner", | |||
| stdout_bytes: bytes, | |||
| stderr_bytes: t.Optional[bytes], | |||
| return_value: t.Any, | |||
| exit_code: int, | |||
| exception: t.Optional[BaseException], | |||
| exc_info: t.Optional[ | |||
| t.Tuple[t.Type[BaseException], BaseException, TracebackType] | |||
| ] = None, | |||
| ): | |||
| #: The runner that created the result | |||
| self.runner = runner | |||
| #: The standard output as bytes. | |||
| self.stdout_bytes = stdout_bytes | |||
| #: The standard error as bytes, or None if not available | |||
| self.stderr_bytes = stderr_bytes | |||
| #: The value returned from the invoked command. | |||
| #: | |||
| #: .. versionadded:: 8.0 | |||
| self.return_value = return_value | |||
| #: The exit code as integer. | |||
| self.exit_code = exit_code | |||
| #: The exception that happened if one did. | |||
| self.exception = exception | |||
| #: The traceback | |||
| self.exc_info = exc_info | |||
| @property | |||
| def output(self) -> str: | |||
| """The (standard) output as unicode string.""" | |||
| return self.stdout | |||
| @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.""" | |||
| if self.stderr_bytes is None: | |||
| raise ValueError("stderr not separately captured") | |||
| 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 stdin writes | |||
| to stdout. This is useful for showing examples in | |||
| some circumstances. Note that regular prompts | |||
| will automatically echo the input. | |||
| :param mix_stderr: if this is set to `False`, then stdout and stderr are | |||
| preserved as independent streams. This is useful for | |||
| Unix-philosophy apps that have predictable stdout and | |||
| noisy stderr, such that each may be measured | |||
| independently | |||
| """ | |||
| def __init__( | |||
| self, | |||
| charset: str = "utf-8", | |||
| env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, | |||
| echo_stdin: bool = False, | |||
| mix_stderr: bool = True, | |||
| ) -> None: | |||
| self.charset = charset | |||
| self.env: t.Mapping[str, t.Optional[str]] = env or {} | |||
| self.echo_stdin = echo_stdin | |||
| self.mix_stderr = mix_stderr | |||
| def get_default_prog_name(self, cli: "BaseCommand") -> 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: t.Optional[t.Mapping[str, t.Optional[str]]] = None | |||
| ) -> t.Mapping[str, t.Optional[str]]: | |||
| """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: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None, | |||
| env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, | |||
| color: bool = False, | |||
| ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: | |||
| """A context manager that sets up the isolation for invoking of a | |||
| command line tool. This sets up stdin 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. | |||
| .. versionchanged:: 8.0 | |||
| ``stderr`` 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) | |||
| bytes_output = io.BytesIO() | |||
| if self.echo_stdin: | |||
| bytes_input = echo_input = t.cast( | |||
| t.BinaryIO, EchoingStdin(bytes_input, bytes_output) | |||
| ) | |||
| sys.stdin = text_input = _NamedTextIOWrapper( | |||
| bytes_input, encoding=self.charset, name="<stdin>", 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( | |||
| bytes_output, encoding=self.charset, name="<stdout>", mode="w" | |||
| ) | |||
| bytes_error = None | |||
| if self.mix_stderr: | |||
| sys.stderr = sys.stdout | |||
| else: | |||
| bytes_error = io.BytesIO() | |||
| sys.stderr = _NamedTextIOWrapper( | |||
| bytes_error, | |||
| encoding=self.charset, | |||
| name="<stderr>", | |||
| mode="w", | |||
| errors="backslashreplace", | |||
| ) | |||
| @_pause_echo(echo_input) # type: ignore | |||
| def visible_input(prompt: t.Optional[str] = None) -> str: | |||
| sys.stdout.write(prompt or "") | |||
| val = text_input.readline().rstrip("\r\n") | |||
| sys.stdout.write(f"{val}\n") | |||
| sys.stdout.flush() | |||
| return val | |||
| @_pause_echo(echo_input) # type: ignore | |||
| def hidden_input(prompt: t.Optional[str] = None) -> str: | |||
| sys.stdout.write(f"{prompt or ''}\n") | |||
| sys.stdout.flush() | |||
| return text_input.readline().rstrip("\r\n") | |||
| @_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.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = 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 | |||
| termui.visible_prompt_func = visible_input | |||
| termui.hidden_prompt_func = hidden_input | |||
| termui._getchar = _getchar | |||
| utils.should_strip_ansi = should_strip_ansi # type: ignore | |||
| 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 (bytes_output, bytes_error) | |||
| 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 | |||
| formatting.FORCED_WIDTH = old_forced_width | |||
| def invoke( | |||
| self, | |||
| cli: "BaseCommand", | |||
| args: t.Optional[t.Union[str, t.Sequence[str]]] = None, | |||
| input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None, | |||
| env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, | |||
| catch_exceptions: bool = True, | |||
| 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``. | |||
| :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. | |||
| .. 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 | |||
| with self.isolation(input=input, env=env, color=color) as outstreams: | |||
| return_value = None | |||
| exception: t.Optional[BaseException] = 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(t.Optional[t.Union[int, t.Any]], 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() | |||
| stdout = outstreams[0].getvalue() | |||
| if self.mix_stderr: | |||
| stderr = None | |||
| else: | |||
| stderr = outstreams[1].getvalue() # type: ignore | |||
| return Result( | |||
| runner=self, | |||
| stdout_bytes=stdout, | |||
| stderr_bytes=stderr, | |||
| return_value=return_value, | |||
| exit_code=exit_code, | |||
| exception=exception, | |||
| exc_info=exc_info, # type: ignore | |||
| ) | |||
| @contextlib.contextmanager | |||
| def isolated_filesystem( | |||
| self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None | |||
| ) -> t.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: | |||
| try: | |||
| shutil.rmtree(dt) | |||
| except OSError: # noqa: B014 | |||
| pass | |||
| @ -0,0 +1,624 @@ | |||
| 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, t.Optional[R]]": | |||
| """Wraps a function so that it swallows exceptions.""" | |||
| def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> t.Optional[R]: | |||
| 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: t.Union[str, "os.PathLike[str]"], | |||
| mode: str = "r", | |||
| encoding: t.Optional[str] = None, | |||
| errors: t.Optional[str] = "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.Optional[t.IO[t.Any]] | |||
| 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"<unopened file '{format_filename(self.name)}' {self.mode}>" | |||
| 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: # noqa: E402 | |||
| 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: t.Optional[t.Type[BaseException]], | |||
| exc_value: t.Optional[BaseException], | |||
| tb: t.Optional[TracebackType], | |||
| ) -> None: | |||
| self.close_intelligently() | |||
| def __iter__(self) -> t.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: t.Optional[t.Type[BaseException]], | |||
| exc_value: t.Optional[BaseException], | |||
| tb: t.Optional[TracebackType], | |||
| ) -> None: | |||
| pass | |||
| def __repr__(self) -> str: | |||
| return repr(self._file) | |||
| def __iter__(self) -> t.Iterator[t.AnyStr]: | |||
| return iter(self._file) | |||
| def echo( | |||
| message: t.Optional[t.Any] = None, | |||
| file: t.Optional[t.IO[t.Any]] = None, | |||
| nl: bool = True, | |||
| err: bool = False, | |||
| color: t.Optional[bool] = 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: t.Optional[t.Union[str, bytes]] = 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) # type: ignore | |||
| elif not color: | |||
| out = strip_ansi(out) | |||
| file.write(out) # type: ignore | |||
| file.flush() | |||
| def get_binary_stream(name: "te.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: "te.Literal['stdin', 'stdout', 'stderr']", | |||
| encoding: t.Optional[str] = None, | |||
| errors: t.Optional[str] = "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, | |||
| mode: str = "r", | |||
| encoding: t.Optional[str] = None, | |||
| errors: t.Optional[str] = "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 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: "t.Union[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\<user>\AppData\Roaming\Foo Bar`` | |||
| Windows (not roaming): | |||
| ``C:\Users\<user>\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: t.Optional[str] = None, _main: t.Optional[ModuleType] = 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: t.Iterable[str], | |||
| *, | |||
| user: bool = True, | |||
| env: bool = True, | |||
| glob_recursive: bool = True, | |||
| ) -> t.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 | |||
| @ -0,0 +1 @@ | |||
| import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim(); | |||
| @ -0,0 +1,49 @@ | |||
| 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'] | |||
| @ -0,0 +1,6 @@ | |||
| """Entry point for cli, enables execution with `python -m dotenv`""" | |||
| from .cli import cli | |||
| if __name__ == "__main__": | |||
| cli() | |||
| @ -0,0 +1,199 @@ | |||
| import json | |||
| import os | |||
| import shlex | |||
| import sys | |||
| from contextlib import contextmanager | |||
| from subprocess import Popen | |||
| from typing import Any, Dict, IO, Iterator, List | |||
| 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(): | |||
| """ | |||
| 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) | |||
| exit(2) | |||
| @cli.command() | |||
| @click.pass_context | |||
| @click.option('--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(ctx: click.Context, format: bool) -> None: | |||
| """Display all the stored key/value.""" | |||
| file = ctx.obj['FILE'] | |||
| with stream_file(file) as stream: | |||
| values = dotenv_values(stream=stream) | |||
| if format == 'json': | |||
| click.echo(json.dumps(values, indent=2, sort_keys=True)) | |||
| else: | |||
| prefix = 'export ' if format == 'export' else '' | |||
| for k in sorted(values): | |||
| v = values[k] | |||
| if v is not None: | |||
| if format in ('export', 'shell'): | |||
| v = shlex.quote(v) | |||
| click.echo(f'{prefix}{k}={v}') | |||
| @cli.command() | |||
| @click.pass_context | |||
| @click.argument('key', required=True) | |||
| @click.argument('value', required=True) | |||
| def set(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: | |||
| 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: | |||
| 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: | |||
| 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.') | |||
| exit(1) | |||
| ret = run_command(commandline, dotenv_as_dict) | |||
| exit(ret) | |||
| def run_command(command: List[str], env: Dict[str, str]) -> int: | |||
| """Run command in sub process. | |||
| Runs the command in a sub process with 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 | |||
| ------- | |||
| int | |||
| The return code of the command | |||
| """ | |||
| # copy the current environment variables and add the vales from | |||
| # `env` | |||
| cmd_env = os.environ.copy() | |||
| cmd_env.update(env) | |||
| p = Popen(command, | |||
| universal_newlines=True, | |||
| bufsize=0, | |||
| shell=False, | |||
| env=cmd_env) | |||
| _, _ = p.communicate() | |||
| return p.returncode | |||
| @ -0,0 +1,39 @@ | |||
| from IPython.core.magic import Magics, line_magic, magics_class # type: ignore | |||
| from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore | |||
| 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) | |||
| @ -0,0 +1,392 @@ | |||
| import io | |||
| import logging | |||
| import os | |||
| import pathlib | |||
| import shutil | |||
| 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 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 os.path.isfile(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 """ | |||
| try: | |||
| main = __import__('__main__', None, None, fromlist=['__file__']) | |||
| except ModuleNotFoundError: | |||
| return False | |||
| return not hasattr(main, '__file__') | |||
| if usecwd or _is_interactive() 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 os.path.isfile(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. | |||
| """ | |||
| 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() | |||
| @ -0,0 +1,175 @@ | |||
| import codecs | |||
| import re | |||
| from typing import (IO, Iterator, Match, NamedTuple, Optional, # noqa:F401 | |||
| Pattern, Sequence, Tuple) | |||
| 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 == u"'": | |||
| (value,) = reader.read_regex(_single_quoted_value) | |||
| return decode_escapes(_single_quote_escapes, value) | |||
| elif char == u'"': | |||
| (value,) = reader.read_regex(_double_quoted_value) | |||
| return decode_escapes(_double_quote_escapes, value) | |||
| elif char in (u"", u"\n", u"\r"): | |||
| return u"" | |||
| 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) | |||
| @ -0,0 +1 @@ | |||
| # Marker file for PEP 561 | |||
| @ -0,0 +1,86 @@ | |||
| import re | |||
| from abc import ABCMeta, abstractmethod | |||
| from typing import Iterator, Mapping, Optional, Pattern | |||
| _posix_variable: Pattern[str] = re.compile( | |||
| r""" | |||
| \$\{ | |||
| (?P<name>[^\}:]*) | |||
| (?::- | |||
| (?P<default>[^\}]*) | |||
| )? | |||
| \} | |||
| """, | |||
| 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]) | |||
| @ -0,0 +1 @@ | |||
| __version__ = "1.0.1" | |||
| @ -0,0 +1 @@ | |||
| pip | |||
| @ -0,0 +1,28 @@ | |||
| Copyright 2010 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. | |||
| @ -0,0 +1,101 @@ | |||
| Metadata-Version: 2.1 | |||
| Name: Flask | |||
| Version: 3.0.3 | |||
| Summary: A simple framework for building complex web applications. | |||
| Maintainer-email: Pallets <contact@palletsprojects.com> | |||
| Requires-Python: >=3.8 | |||
| Description-Content-Type: text/markdown | |||
| Classifier: Development Status :: 5 - Production/Stable | |||
| Classifier: Environment :: Web Environment | |||
| Classifier: Framework :: Flask | |||
| Classifier: Intended Audience :: Developers | |||
| Classifier: License :: OSI Approved :: BSD License | |||
| Classifier: Operating System :: OS Independent | |||
| Classifier: Programming Language :: Python | |||
| Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content | |||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI | |||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application | |||
| Classifier: Topic :: Software Development :: Libraries :: Application Frameworks | |||
| Classifier: Typing :: Typed | |||
| Requires-Dist: Werkzeug>=3.0.0 | |||
| Requires-Dist: Jinja2>=3.1.2 | |||
| Requires-Dist: itsdangerous>=2.1.2 | |||
| Requires-Dist: click>=8.1.3 | |||
| Requires-Dist: blinker>=1.6.2 | |||
| Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10' | |||
| Requires-Dist: asgiref>=3.2 ; extra == "async" | |||
| Requires-Dist: python-dotenv ; extra == "dotenv" | |||
| Project-URL: Changes, https://flask.palletsprojects.com/changes/ | |||
| Project-URL: Chat, https://discord.gg/pallets | |||
| Project-URL: Documentation, https://flask.palletsprojects.com/ | |||
| Project-URL: Donate, https://palletsprojects.com/donate | |||
| Project-URL: Source, https://github.com/pallets/flask/ | |||
| Provides-Extra: async | |||
| Provides-Extra: dotenv | |||
| # Flask | |||
| Flask is a lightweight [WSGI][] web application framework. It is designed | |||
| to make getting started quick and easy, with the ability to scale up to | |||
| complex applications. It began as a simple wrapper around [Werkzeug][] | |||
| and [Jinja][], and has become one of the most popular Python web | |||
| application frameworks. | |||
| Flask offers suggestions, but doesn't enforce any dependencies or | |||
| project layout. It is up to the developer to choose the tools and | |||
| libraries they want to use. There are many extensions provided by the | |||
| community that make adding new functionality easy. | |||
| [WSGI]: https://wsgi.readthedocs.io/ | |||
| [Werkzeug]: https://werkzeug.palletsprojects.com/ | |||
| [Jinja]: https://jinja.palletsprojects.com/ | |||
| ## Installing | |||
| Install and update from [PyPI][] using an installer such as [pip][]: | |||
| ``` | |||
| $ pip install -U Flask | |||
| ``` | |||
| [PyPI]: https://pypi.org/project/Flask/ | |||
| [pip]: https://pip.pypa.io/en/stable/getting-started/ | |||
| ## A Simple Example | |||
| ```python | |||
| # save this as app.py | |||
| from flask import Flask | |||
| app = Flask(__name__) | |||
| @app.route("/") | |||
| def hello(): | |||
| return "Hello, World!" | |||
| ``` | |||
| ``` | |||
| $ flask run | |||
| * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) | |||
| ``` | |||
| ## Contributing | |||
| For guidance on setting up a development environment and how to make a | |||
| contribution to Flask, see the [contributing guidelines][]. | |||
| [contributing guidelines]: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst | |||
| ## Donate | |||
| The Pallets organization develops and supports Flask and the libraries | |||
| it uses. 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 | |||
| @ -0,0 +1,58 @@ | |||
| ../../../bin/flask,sha256=WGkUeszXfSRxQKPMcg5-RJwbk3v8E5yJViSaAaGdwNE,249 | |||
| flask-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
| flask-3.0.3.dist-info/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 | |||
| flask-3.0.3.dist-info/METADATA,sha256=exPahy4aahjV-mYqd9qb5HNP8haB_IxTuaotoSvCtag,3177 | |||
| flask-3.0.3.dist-info/RECORD,, | |||
| flask-3.0.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| flask-3.0.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 | |||
| flask-3.0.3.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40 | |||
| flask/__init__.py,sha256=6xMqdVA0FIQ2U1KVaGX3lzNCdXPzoHPaa0hvQCNcfSk,2625 | |||
| flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 | |||
| flask/__pycache__/__init__.cpython-310.pyc,, | |||
| flask/__pycache__/__main__.cpython-310.pyc,, | |||
| flask/__pycache__/app.cpython-310.pyc,, | |||
| flask/__pycache__/blueprints.cpython-310.pyc,, | |||
| flask/__pycache__/cli.cpython-310.pyc,, | |||
| flask/__pycache__/config.cpython-310.pyc,, | |||
| flask/__pycache__/ctx.cpython-310.pyc,, | |||
| flask/__pycache__/debughelpers.cpython-310.pyc,, | |||
| flask/__pycache__/globals.cpython-310.pyc,, | |||
| flask/__pycache__/helpers.cpython-310.pyc,, | |||
| flask/__pycache__/logging.cpython-310.pyc,, | |||
| flask/__pycache__/sessions.cpython-310.pyc,, | |||
| flask/__pycache__/signals.cpython-310.pyc,, | |||
| flask/__pycache__/templating.cpython-310.pyc,, | |||
| flask/__pycache__/testing.cpython-310.pyc,, | |||
| flask/__pycache__/typing.cpython-310.pyc,, | |||
| flask/__pycache__/views.cpython-310.pyc,, | |||
| flask/__pycache__/wrappers.cpython-310.pyc,, | |||
| flask/app.py,sha256=7-lh6cIj27riTE1Q18Ok1p5nOZ8qYiMux4Btc6o6mNc,60143 | |||
| flask/blueprints.py,sha256=7INXPwTkUxfOQXOOv1yu52NpHPmPGI5fMTMFZ-BG9yY,4430 | |||
| flask/cli.py,sha256=OOaf_Efqih1i2in58j-5ZZZmQnPpaSfiUFbEjlL9bzw,35825 | |||
| flask/config.py,sha256=bLzLVAj-cq-Xotu9erqOFte0xSFaVXyfz0AkP4GbwmY,13312 | |||
| flask/ctx.py,sha256=4atDhJJ_cpV1VMq4qsfU4E_61M1oN93jlS2H9gjrl58,15120 | |||
| flask/debughelpers.py,sha256=PGIDhStW_efRjpaa3zHIpo-htStJOR41Ip3OJWPYBwo,6080 | |||
| flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713 | |||
| flask/helpers.py,sha256=tYrcQ_73GuSZVEgwFr-eMmV69UriFQDBmt8wZJIAqvg,23084 | |||
| flask/json/__init__.py,sha256=hLNR898paqoefdeAhraa5wyJy-bmRB2k2dV4EgVy2Z8,5602 | |||
| flask/json/__pycache__/__init__.cpython-310.pyc,, | |||
| flask/json/__pycache__/provider.cpython-310.pyc,, | |||
| flask/json/__pycache__/tag.cpython-310.pyc,, | |||
| flask/json/provider.py,sha256=q6iB83lSiopy80DZPrU-9mGcWwrD0mvLjiv9fHrRZgc,7646 | |||
| flask/json/tag.py,sha256=DhaNwuIOhdt2R74oOC9Y4Z8ZprxFYiRb5dUP5byyINw,9281 | |||
| flask/logging.py,sha256=8sM3WMTubi1cBb2c_lPkWpN0J8dMAqrgKRYLLi1dCVI,2377 | |||
| flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
| flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228 | |||
| flask/sansio/__pycache__/app.cpython-310.pyc,, | |||
| flask/sansio/__pycache__/blueprints.cpython-310.pyc,, | |||
| flask/sansio/__pycache__/scaffold.cpython-310.pyc,, | |||
| flask/sansio/app.py,sha256=YG5Gf7JVf1c0yccWDZ86q5VSfJUidOVp27HFxFNxC7U,38053 | |||
| flask/sansio/blueprints.py,sha256=Tqe-7EkZ-tbWchm8iDoCfD848f0_3nLv6NNjeIPvHwM,24637 | |||
| flask/sansio/scaffold.py,sha256=WLV9TRQMMhGlXz-1OKtQ3lv6mtIBQZxdW2HezYrGxoI,30633 | |||
| flask/sessions.py,sha256=RU4lzm9MQW9CtH8rVLRTDm8USMJyT4LbvYe7sxM2__k,14807 | |||
| flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750 | |||
| flask/templating.py,sha256=2TcXLT85Asflm2W9WOSFxKCmYn5e49w_Jkg9-NaaJWo,7537 | |||
| flask/testing.py,sha256=3BFXb3bP7R5r-XLBuobhczbxDu8-1LWRzYuhbr-lwaE,10163 | |||
| flask/typing.py,sha256=ZavK-wV28Yv8CQB7u73qZp_jLalpbWdrXS37QR1ftN0,3190 | |||
| flask/views.py,sha256=B66bTvYBBcHMYk4dA1ScZD0oTRTBl0I5smp1lRm9riI,6939 | |||
| flask/wrappers.py,sha256=m1j5tIJxIu8_sPPgTAB_G4TTh52Q-HoDuw_qHV5J59g,5831 | |||
| @ -0,0 +1,4 @@ | |||
| Wheel-Version: 1.0 | |||
| Generator: flit 3.9.0 | |||
| Root-Is-Purelib: true | |||
| Tag: py3-none-any | |||
| @ -0,0 +1,3 @@ | |||
| [console_scripts] | |||
| flask=flask.cli:main | |||
| @ -0,0 +1,60 @@ | |||
| from __future__ import annotations | |||
| import typing as t | |||
| from . import json as json | |||
| from .app import Flask as Flask | |||
| from .blueprints import Blueprint as Blueprint | |||
| from .config import Config as Config | |||
| from .ctx import after_this_request as after_this_request | |||
| from .ctx import copy_current_request_context as copy_current_request_context | |||
| from .ctx import has_app_context as has_app_context | |||
| from .ctx import has_request_context as has_request_context | |||
| from .globals import current_app as current_app | |||
| from .globals import g as g | |||
| from .globals import request as request | |||
| from .globals import session as session | |||
| from .helpers import abort as abort | |||
| from .helpers import flash as flash | |||
| from .helpers import get_flashed_messages as get_flashed_messages | |||
| from .helpers import get_template_attribute as get_template_attribute | |||
| from .helpers import make_response as make_response | |||
| from .helpers import redirect as redirect | |||
| from .helpers import send_file as send_file | |||
| from .helpers import send_from_directory as send_from_directory | |||
| from .helpers import stream_with_context as stream_with_context | |||
| from .helpers import url_for as url_for | |||
| from .json import jsonify as jsonify | |||
| from .signals import appcontext_popped as appcontext_popped | |||
| from .signals import appcontext_pushed as appcontext_pushed | |||
| from .signals import appcontext_tearing_down as appcontext_tearing_down | |||
| from .signals import before_render_template as before_render_template | |||
| from .signals import got_request_exception as got_request_exception | |||
| from .signals import message_flashed as message_flashed | |||
| from .signals import request_finished as request_finished | |||
| from .signals import request_started as request_started | |||
| from .signals import request_tearing_down as request_tearing_down | |||
| from .signals import template_rendered as template_rendered | |||
| from .templating import render_template as render_template | |||
| from .templating import render_template_string as render_template_string | |||
| from .templating import stream_template as stream_template | |||
| from .templating import stream_template_string as stream_template_string | |||
| from .wrappers import Request as Request | |||
| from .wrappers import Response as Response | |||
| def __getattr__(name: str) -> t.Any: | |||
| if name == "__version__": | |||
| import importlib.metadata | |||
| import warnings | |||
| warnings.warn( | |||
| "The '__version__' attribute is deprecated and will be removed in" | |||
| " Flask 3.1. Use feature detection or" | |||
| " 'importlib.metadata.version(\"flask\")' instead.", | |||
| DeprecationWarning, | |||
| stacklevel=2, | |||
| ) | |||
| return importlib.metadata.version("flask") | |||
| raise AttributeError(name) | |||
| @ -0,0 +1,3 @@ | |||
| from .cli import main | |||
| main() | |||
| @ -0,0 +1,129 @@ | |||
| from __future__ import annotations | |||
| import os | |||
| import typing as t | |||
| from datetime import timedelta | |||
| from .cli import AppGroup | |||
| from .globals import current_app | |||
| from .helpers import send_from_directory | |||
| from .sansio.blueprints import Blueprint as SansioBlueprint | |||
| from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa | |||
| from .sansio.scaffold import _sentinel | |||
| if t.TYPE_CHECKING: # pragma: no cover | |||
| from .wrappers import Response | |||
| class Blueprint(SansioBlueprint): | |||
| def __init__( | |||
| self, | |||
| name: str, | |||
| import_name: str, | |||
| static_folder: str | os.PathLike[str] | None = None, | |||
| static_url_path: str | None = None, | |||
| template_folder: str | os.PathLike[str] | None = None, | |||
| url_prefix: str | None = None, | |||
| subdomain: str | None = None, | |||
| url_defaults: dict[str, t.Any] | None = None, | |||
| root_path: str | None = None, | |||
| cli_group: str | None = _sentinel, # type: ignore | |||
| ) -> None: | |||
| super().__init__( | |||
| name, | |||
| import_name, | |||
| static_folder, | |||
| static_url_path, | |||
| template_folder, | |||
| url_prefix, | |||
| subdomain, | |||
| url_defaults, | |||
| root_path, | |||
| cli_group, | |||
| ) | |||
| #: The Click command group for registering CLI commands for this | |||
| #: object. The commands are available from the ``flask`` command | |||
| #: once the application has been discovered and blueprints have | |||
| #: been registered. | |||
| self.cli = AppGroup() | |||
| # Set the name of the Click group in case someone wants to add | |||
| # the app's commands to another CLI tool. | |||
| self.cli.name = self.name | |||
| def get_send_file_max_age(self, filename: str | None) -> int | None: | |||
| """Used by :func:`send_file` to determine the ``max_age`` cache | |||
| value for a given file path if it wasn't passed. | |||
| By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from | |||
| the configuration of :data:`~flask.current_app`. This defaults | |||
| to ``None``, which tells the browser to use conditional requests | |||
| instead of a timed cache, which is usually preferable. | |||
| Note this is a duplicate of the same method in the Flask | |||
| class. | |||
| .. versionchanged:: 2.0 | |||
| The default configuration is ``None`` instead of 12 hours. | |||
| .. versionadded:: 0.9 | |||
| """ | |||
| value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] | |||
| if value is None: | |||
| return None | |||
| if isinstance(value, timedelta): | |||
| return int(value.total_seconds()) | |||
| return value # type: ignore[no-any-return] | |||
| def send_static_file(self, filename: str) -> Response: | |||
| """The view function used to serve files from | |||
| :attr:`static_folder`. A route is automatically registered for | |||
| this view at :attr:`static_url_path` if :attr:`static_folder` is | |||
| set. | |||
| Note this is a duplicate of the same method in the Flask | |||
| class. | |||
| .. versionadded:: 0.5 | |||
| """ | |||
| if not self.has_static_folder: | |||
| raise RuntimeError("'static_folder' must be set to serve static_files.") | |||
| # send_file only knows to call get_send_file_max_age on the app, | |||
| # call it here so it works for blueprints too. | |||
| max_age = self.get_send_file_max_age(filename) | |||
| return send_from_directory( | |||
| t.cast(str, self.static_folder), filename, max_age=max_age | |||
| ) | |||
| def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: | |||
| """Open a resource file relative to :attr:`root_path` for | |||
| reading. | |||
| For example, if the file ``schema.sql`` is next to the file | |||
| ``app.py`` where the ``Flask`` app is defined, it can be opened | |||
| with: | |||
| .. code-block:: python | |||
| with app.open_resource("schema.sql") as f: | |||
| conn.executescript(f.read()) | |||
| :param resource: Path to the resource relative to | |||
| :attr:`root_path`. | |||
| :param mode: Open the file in this mode. Only reading is | |||
| supported, valid values are "r" (or "rt") and "rb". | |||
| Note this is a duplicate of the same method in the Flask | |||
| class. | |||
| """ | |||
| if mode not in {"r", "rt", "rb"}: | |||
| raise ValueError("Resources can only be opened for reading.") | |||
| return open(os.path.join(self.root_path, resource), mode) | |||