初始化
This commit is contained in:
commit
36f8ef7da9
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local development
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.db
|
||||||
241
.venv/bin/Activate.ps1
Normal file
241
.venv/bin/Activate.ps1
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<#
|
||||||
|
.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 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 virutal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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"
|
||||||
66
.venv/bin/activate
Normal file
66
.venv/bin/activate
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# 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
|
||||||
|
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
VIRTUAL_ENV="/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||||
|
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="(.venv) ${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
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
|
||||||
25
.venv/bin/activate.csh
Normal file
25
.venv/bin/activate.csh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 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; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV "/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
set prompt = "(.venv) $prompt"
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
64
.venv/bin/activate.fish
Normal file
64
.venv/bin/activate.fish
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# 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"
|
||||||
|
functions -e fish_prompt
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self-destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV "/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# With the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command.
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
printf "%s%s%s" (set_color 4B8BBE) "(.venv) " (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
# Output the original/"old" prompt.
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
end
|
||||||
8
.venv/bin/pip
Executable file
8
.venv/bin/pip
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
.venv/bin/pip3
Executable file
8
.venv/bin/pip3
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
.venv/bin/pip3.9
Executable file
8
.venv/bin/pip3.9
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
1
.venv/bin/python
Symbolic link
1
.venv/bin/python
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
1
.venv/bin/python3
Symbolic link
1
.venv/bin/python3
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/Library/Developer/CommandLineTools/usr/bin/python3
|
||||||
1
.venv/bin/python3.9
Symbolic link
1
.venv/bin/python3.9
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
3
.venv/pyvenv.cfg
Normal file
3
.venv/pyvenv.cfg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
home = /Library/Developer/CommandLineTools/usr/bin
|
||||||
|
include-system-site-packages = false
|
||||||
|
version = 3.9.6
|
||||||
13
app/api/deps.py
Normal file
13
app/api/deps.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from fastapi import Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.models.database import get_db
|
||||||
|
from app.models.user import UserDB
|
||||||
|
|
||||||
|
async def get_current_user(
|
||||||
|
phone: str,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
) -> UserDB:
|
||||||
|
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=401, detail="用户未登录")
|
||||||
|
return user
|
||||||
132
app/api/endpoints/address.py
Normal file
132
app/api/endpoints/address.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import and_
|
||||||
|
from typing import List
|
||||||
|
from app.models.address import AddressDB, AddressCreate, AddressUpdate, AddressInfo
|
||||||
|
from app.models.database import get_db
|
||||||
|
from app.api.deps import get_current_user
|
||||||
|
from app.models.user import UserDB
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/", response_model=AddressInfo)
|
||||||
|
async def create_address(
|
||||||
|
address: AddressCreate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""创建配送地址"""
|
||||||
|
# 如果设置为默认地址,先将其他地址的默认状态取消
|
||||||
|
if address.is_default:
|
||||||
|
db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.user_id == current_user.userid,
|
||||||
|
AddressDB.is_default == True
|
||||||
|
)
|
||||||
|
).update({"is_default": False})
|
||||||
|
|
||||||
|
db_address = AddressDB(
|
||||||
|
user_id=current_user.userid,
|
||||||
|
**address.model_dump()
|
||||||
|
)
|
||||||
|
db.add(db_address)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_address)
|
||||||
|
return db_address
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[AddressInfo])
|
||||||
|
async def get_addresses(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取用户的所有配送地址"""
|
||||||
|
addresses = db.query(AddressDB).filter(
|
||||||
|
AddressDB.user_id == current_user.userid
|
||||||
|
).all()
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
@router.put("/{address_id}", response_model=AddressInfo)
|
||||||
|
async def update_address(
|
||||||
|
address_id: int,
|
||||||
|
address: AddressUpdate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""更新配送地址"""
|
||||||
|
db_address = db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.id == address_id,
|
||||||
|
AddressDB.user_id == current_user.userid
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not db_address:
|
||||||
|
raise HTTPException(status_code=404, detail="地址不存在")
|
||||||
|
|
||||||
|
# 如果设置为默认地址,先将其他地址的默认状态取消
|
||||||
|
update_data = address.model_dump(exclude_unset=True)
|
||||||
|
if update_data.get("is_default"):
|
||||||
|
db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.user_id == current_user.userid,
|
||||||
|
AddressDB.is_default == True
|
||||||
|
)
|
||||||
|
).update({"is_default": False})
|
||||||
|
|
||||||
|
for key, value in update_data.items():
|
||||||
|
setattr(db_address, key, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_address)
|
||||||
|
return db_address
|
||||||
|
|
||||||
|
@router.delete("/{address_id}")
|
||||||
|
async def delete_address(
|
||||||
|
address_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""删除配送地址"""
|
||||||
|
result = db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.id == address_id,
|
||||||
|
AddressDB.user_id == current_user.userid
|
||||||
|
)
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise HTTPException(status_code=404, detail="地址不存在")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return {"message": "地址已删除"}
|
||||||
|
|
||||||
|
@router.post("/{address_id}/set-default", response_model=AddressInfo)
|
||||||
|
async def set_default_address(
|
||||||
|
address_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""设置默认地址"""
|
||||||
|
# 取消其他默认地址
|
||||||
|
db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.user_id == current_user.userid,
|
||||||
|
AddressDB.is_default == True
|
||||||
|
)
|
||||||
|
).update({"is_default": False})
|
||||||
|
|
||||||
|
# 设置新的默认地址
|
||||||
|
db_address = db.query(AddressDB).filter(
|
||||||
|
and_(
|
||||||
|
AddressDB.id == address_id,
|
||||||
|
AddressDB.user_id == current_user.userid
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not db_address:
|
||||||
|
raise HTTPException(status_code=404, detail="地址不存在")
|
||||||
|
|
||||||
|
db_address.is_default = True
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_address)
|
||||||
|
return db_address
|
||||||
72
app/api/endpoints/community.py
Normal file
72
app/api/endpoints/community.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List
|
||||||
|
from app.models.community import CommunityDB, CommunityCreate, CommunityUpdate, CommunityInfo
|
||||||
|
from app.models.database import get_db
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/", response_model=CommunityInfo)
|
||||||
|
async def create_community(
|
||||||
|
community: CommunityCreate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""创建社区"""
|
||||||
|
db_community = CommunityDB(**community.model_dump())
|
||||||
|
db.add(db_community)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_community)
|
||||||
|
return db_community
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[CommunityInfo])
|
||||||
|
async def get_communities(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 10,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取社区列表"""
|
||||||
|
communities = db.query(CommunityDB).offset(skip).limit(limit).all()
|
||||||
|
return communities
|
||||||
|
|
||||||
|
@router.get("/{community_id}", response_model=CommunityInfo)
|
||||||
|
async def get_community(
|
||||||
|
community_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取社区详情"""
|
||||||
|
community = db.query(CommunityDB).filter(CommunityDB.id == community_id).first()
|
||||||
|
if not community:
|
||||||
|
raise HTTPException(status_code=404, detail="社区不存在")
|
||||||
|
return community
|
||||||
|
|
||||||
|
@router.put("/{community_id}", response_model=CommunityInfo)
|
||||||
|
async def update_community(
|
||||||
|
community_id: int,
|
||||||
|
community: CommunityUpdate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""更新社区信息"""
|
||||||
|
db_community = db.query(CommunityDB).filter(CommunityDB.id == community_id).first()
|
||||||
|
if not db_community:
|
||||||
|
raise HTTPException(status_code=404, detail="社区不存在")
|
||||||
|
|
||||||
|
update_data = community.model_dump(exclude_unset=True)
|
||||||
|
for key, value in update_data.items():
|
||||||
|
setattr(db_community, key, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_community)
|
||||||
|
return db_community
|
||||||
|
|
||||||
|
@router.delete("/{community_id}")
|
||||||
|
async def delete_community(
|
||||||
|
community_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""删除社区"""
|
||||||
|
result = db.query(CommunityDB).filter(CommunityDB.id == community_id).delete()
|
||||||
|
if not result:
|
||||||
|
raise HTTPException(status_code=404, detail="社区不存在")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return {"message": "社区已删除"}
|
||||||
92
app/api/endpoints/user.py
Normal file
92
app/api/endpoints/user.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.models.user import UserLogin, UserInfo, VerifyCodeRequest, UserDB
|
||||||
|
from app.models.database import get_db
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import redis
|
||||||
|
from app.core.config import settings
|
||||||
|
from unisdk.sms import UniSMS
|
||||||
|
from unisdk.exception import UniException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Redis 连接
|
||||||
|
redis_client = redis.Redis(
|
||||||
|
host=settings.REDIS_HOST,
|
||||||
|
port=settings.REDIS_PORT,
|
||||||
|
db=settings.REDIS_DB,
|
||||||
|
password=settings.REDIS_PASSWORD,
|
||||||
|
decode_responses=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 初始化短信客户端
|
||||||
|
client = UniSMS(settings.UNI_APP_ID)
|
||||||
|
|
||||||
|
@router.post("/send-code")
|
||||||
|
async def send_verify_code(request: VerifyCodeRequest):
|
||||||
|
"""发送验证码"""
|
||||||
|
phone = request.phone
|
||||||
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送短信
|
||||||
|
res = client.send({
|
||||||
|
"to": phone,
|
||||||
|
"signature": settings.UNI_SMS_SIGN,
|
||||||
|
"templateId": settings.UNI_SMS_TEMPLATE_ID,
|
||||||
|
"templateData": {
|
||||||
|
"code": code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if res.code != "0": # 0 表示发送成功
|
||||||
|
raise HTTPException(status_code=500, detail=f"短信发送失败: {res.message}")
|
||||||
|
|
||||||
|
# 存储验证码到 Redis
|
||||||
|
redis_client.setex(
|
||||||
|
f"verify_code:{phone}",
|
||||||
|
settings.VERIFICATION_CODE_EXPIRE_SECONDS,
|
||||||
|
code
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"message": "验证码已发送"}
|
||||||
|
|
||||||
|
except UniException as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"发送验证码失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/login")
|
||||||
|
async def login(user_login: UserLogin, db: Session = Depends(get_db)):
|
||||||
|
"""用户登录"""
|
||||||
|
phone = user_login.phone
|
||||||
|
verify_code = user_login.verify_code
|
||||||
|
|
||||||
|
# 验证验证码
|
||||||
|
stored_code = redis_client.get(f"verify_code:{phone}")
|
||||||
|
if not stored_code or stored_code != verify_code:
|
||||||
|
raise HTTPException(status_code=400, detail="验证码错误或已过期")
|
||||||
|
|
||||||
|
redis_client.delete(f"verify_code:{phone}")
|
||||||
|
|
||||||
|
# 查找或创建用户
|
||||||
|
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||||
|
if not user:
|
||||||
|
# 创建新用户
|
||||||
|
user = UserDB(
|
||||||
|
username=f"user_{phone[-4:]}",
|
||||||
|
phone=phone
|
||||||
|
)
|
||||||
|
db.add(user)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(user)
|
||||||
|
|
||||||
|
return {"message": "登录成功", "user": UserInfo.model_validate(user)}
|
||||||
|
|
||||||
|
@router.get("/info")
|
||||||
|
async def get_user_info(phone: str, db: Session = Depends(get_db)):
|
||||||
|
"""获取用户信息"""
|
||||||
|
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
|
return UserInfo.model_validate(user)
|
||||||
39
app/core/config.py
Normal file
39
app/core/config.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
API_V1_STR: str = "/api/v1"
|
||||||
|
PROJECT_NAME: str = "FastAPI 项目"
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_URL: str = "sqlite:///./sql_app.db"
|
||||||
|
|
||||||
|
# JWT 配置
|
||||||
|
SECRET_KEY: str = "your-secret-key-here"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||||
|
|
||||||
|
REDIS_HOST: str = "101.36.120.145"
|
||||||
|
REDIS_PORT: int = 6379
|
||||||
|
REDIS_DB: int = 0
|
||||||
|
REDIS_PASSWORD: str = "redis_rJRMHr"
|
||||||
|
VERIFICATION_CODE_EXPIRE_SECONDS: int = 300 # 验证码5分钟后过期
|
||||||
|
|
||||||
|
# 短信配置
|
||||||
|
UNI_APP_ID: str = "kFb5kA5EDXpnzUReadaRNpDTFf6rNmXEc45jwS2C1Mvh9Erj2"
|
||||||
|
UNI_SMS_TEMPLATE_ID: str = "pub_verif_basic" # 验证码短信模板ID
|
||||||
|
UNI_SMS_SIGN: str = "BAISIJI" # 短信签名
|
||||||
|
|
||||||
|
# MySQL配置
|
||||||
|
MYSQL_HOST: str = "101.36.120.145"
|
||||||
|
MYSQL_PORT: int = 3306
|
||||||
|
MYSQL_USER: str = "root"
|
||||||
|
MYSQL_PASSWORD: str = "mariadb_4rMwpT"
|
||||||
|
MYSQL_DB: str = "deliveryman"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def SQLALCHEMY_DATABASE_URL(self) -> str:
|
||||||
|
return f"mysql+pymysql://{self.MYSQL_USER}:{self.MYSQL_PASSWORD}@{self.MYSQL_HOST}:{self.MYSQL_PORT}/{self.MYSQL_DB}?charset=utf8mb4"
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
35
app/main.py
Normal file
35
app/main.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from app.api.endpoints import user, address, community
|
||||||
|
from app.models.database import Base, engine
|
||||||
|
|
||||||
|
# 创建数据库表
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="闪兔到家",
|
||||||
|
description="API 文档",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置 CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加用户路由
|
||||||
|
app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
||||||
|
app.include_router(address.router, prefix="/api/address", tags=["配送地址"])
|
||||||
|
app.include_router(community.router, prefix="/api/community", tags=["社区"])
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "欢迎使用 FastAPI!"}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy"}
|
||||||
45
app/models/address.py
Normal file
45
app/models/address.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
# 数据库模型
|
||||||
|
class AddressDB(Base):
|
||||||
|
__tablename__ = "delivery_addresses"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.userid"), index=True)
|
||||||
|
community_id = Column(Integer, index=True)
|
||||||
|
address_detail = Column(String(200))
|
||||||
|
name = Column(String(50))
|
||||||
|
phone = Column(String(11))
|
||||||
|
is_default = Column(Boolean, default=False)
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class AddressCreate(BaseModel):
|
||||||
|
community_id: int
|
||||||
|
address_detail: str = Field(..., max_length=200)
|
||||||
|
name: str = Field(..., max_length=50)
|
||||||
|
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||||
|
is_default: bool = False
|
||||||
|
|
||||||
|
class AddressUpdate(BaseModel):
|
||||||
|
community_id: Optional[int] = None
|
||||||
|
address_detail: Optional[str] = Field(None, max_length=200)
|
||||||
|
name: Optional[str] = Field(None, max_length=50)
|
||||||
|
phone: Optional[str] = Field(None, pattern="^1[3-9]\d{9}$")
|
||||||
|
is_default: Optional[bool] = None
|
||||||
|
|
||||||
|
class AddressInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
community_id: int
|
||||||
|
address_detail: str
|
||||||
|
name: str
|
||||||
|
phone: str
|
||||||
|
is_default: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
40
app/models/community.py
Normal file
40
app/models/community.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy import Column, Integer, String, Float, DateTime
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
# 数据库模型
|
||||||
|
class CommunityDB(Base):
|
||||||
|
__tablename__ = "communities"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
address = Column(String(200), nullable=False)
|
||||||
|
longitude = Column(Float, nullable=False) # 经度
|
||||||
|
latitude = Column(Float, nullable=False) # 纬度
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class CommunityCreate(BaseModel):
|
||||||
|
name: str = Field(..., max_length=100)
|
||||||
|
address: str = Field(..., max_length=200)
|
||||||
|
longitude: float = Field(..., ge=-180, le=180)
|
||||||
|
latitude: float = Field(..., ge=-90, le=90)
|
||||||
|
|
||||||
|
class CommunityUpdate(BaseModel):
|
||||||
|
name: Optional[str] = Field(None, max_length=100)
|
||||||
|
address: Optional[str] = Field(None, max_length=200)
|
||||||
|
longitude: Optional[float] = Field(None, ge=-180, le=180)
|
||||||
|
latitude: Optional[float] = Field(None, ge=-90, le=90)
|
||||||
|
|
||||||
|
class CommunityInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
address: str
|
||||||
|
longitude: float
|
||||||
|
latitude: float
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
25
app/models/database.py
Normal file
25
app/models/database.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from app.core.config import settings
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
# 注册 MySQL Python SQL Driver
|
||||||
|
pymysql.install_as_MySQLdb()
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
settings.SQLALCHEMY_DATABASE_URL,
|
||||||
|
pool_pre_ping=True, # 自动处理断开的连接
|
||||||
|
pool_recycle=3600, # 连接回收时间
|
||||||
|
)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
# 依赖项
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
30
app/models/user.py
Normal file
30
app/models/user.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from sqlalchemy import Column, String, DateTime,Integer
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
# 数据库模型
|
||||||
|
class UserDB(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
userid = Column(Integer, primary_key=True,autoincrement=True, index=True)
|
||||||
|
username = Column(String(50))
|
||||||
|
phone = Column(String(11), unique=True, index=True)
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class UserLogin(BaseModel):
|
||||||
|
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||||
|
verify_code: str = Field(..., min_length=6, max_length=6)
|
||||||
|
|
||||||
|
class UserInfo(BaseModel):
|
||||||
|
userid: int
|
||||||
|
username: str
|
||||||
|
phone: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class VerifyCodeRequest(BaseModel):
|
||||||
|
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||||
15
project_structure
Normal file
15
project_structure
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
my_fastapi_project/
|
||||||
|
├── app/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── main.py
|
||||||
|
│ ├── api/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── endpoints/
|
||||||
|
│ │ └── __init__.py
|
||||||
|
│ ├── core/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── config.py
|
||||||
|
│ └── models/
|
||||||
|
│ └── __init__.py
|
||||||
|
├── requirements.txt
|
||||||
|
└── README.md
|
||||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
fastapi>=0.68.0
|
||||||
|
uvicorn>=0.15.0
|
||||||
|
pydantic>=2.0.0
|
||||||
|
pydantic-settings>=2.0.0
|
||||||
|
python-jose>=3.3.0
|
||||||
|
passlib>=1.7.4
|
||||||
|
python-multipart>=0.0.5
|
||||||
|
sqlalchemy>=1.4.23
|
||||||
|
redis==5.0.1
|
||||||
|
pymysql==1.1.0
|
||||||
|
SQLAlchemy==2.0.27
|
||||||
|
unisms
|
||||||
Loading…
Reference in New Issue
Block a user