| @ -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) | |||||