add email and password verification to client-side vpn
This commit is contained in:
parent
10f8e81d92
commit
693930cd7b
192
cli.py
Executable file
192
cli.py
Executable file
@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
VPN Access Server - Main Entry Point
|
||||
|
||||
Unified command-line interface for all VPN Access Server operations.
|
||||
Supports authentication, session management, database operations, and testing.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add the access module to the Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'access'))
|
||||
|
||||
def run_auth():
|
||||
"""Run authentication module."""
|
||||
from access.auth import main as auth_main
|
||||
auth_main()
|
||||
|
||||
def run_session():
|
||||
"""Run session management module."""
|
||||
from access.session import main as session_main
|
||||
session_main()
|
||||
|
||||
def run_init_db():
|
||||
"""Initialize database schema."""
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts'))
|
||||
from scripts.init_db import main as init_db_main
|
||||
init_db_main()
|
||||
|
||||
def run_seed_data():
|
||||
"""Seed database with sample data."""
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts'))
|
||||
from scripts.seed_data import main as seed_data_main
|
||||
seed_data_main()
|
||||
|
||||
def run_tests():
|
||||
"""Run unit tests."""
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run([
|
||||
sys.executable, '-m', 'pytest', 'tests/', '-v'
|
||||
], cwd=os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.exit(result.returncode)
|
||||
except FileNotFoundError:
|
||||
print("pytest not found. Install test dependencies with: uv add --group test pytest pytest-cov")
|
||||
sys.exit(1)
|
||||
|
||||
def health_check():
|
||||
"""Check system health and connectivity."""
|
||||
from access.config import config
|
||||
from access.db import db
|
||||
from access.utils import setup_logging
|
||||
|
||||
logger = setup_logging(log_level='INFO')
|
||||
|
||||
print("🔍 VPN Access Server Health Check")
|
||||
print("=" * 50)
|
||||
|
||||
# Configuration check
|
||||
print("📋 Configuration...")
|
||||
if config.validate():
|
||||
print("✅ Configuration is valid")
|
||||
else:
|
||||
print("❌ Configuration validation failed")
|
||||
return False
|
||||
|
||||
# Database check
|
||||
print("🗄️ Database connectivity...")
|
||||
if db.health_check():
|
||||
print("✅ Database connection successful")
|
||||
else:
|
||||
print("❌ Database connection failed")
|
||||
return False
|
||||
|
||||
# Test utilities
|
||||
print("🔧 Utility functions...")
|
||||
from access.utils import validate_mac_address, normalize_mac_address
|
||||
test_mac = "00:11:22:33:44:55"
|
||||
if validate_mac_address(test_mac) and normalize_mac_address("001122334455") == test_mac:
|
||||
print("✅ Utility functions working")
|
||||
else:
|
||||
print("❌ Utility functions failed")
|
||||
return False
|
||||
|
||||
print("=" * 50)
|
||||
print("✅ All health checks passed!")
|
||||
return True
|
||||
|
||||
def show_status():
|
||||
"""Show system status and configuration."""
|
||||
from access.config import config
|
||||
|
||||
print("📊 VPN Access Server Status")
|
||||
print("=" * 50)
|
||||
print(f"Database Host: {config.database.host}:{config.database.port}")
|
||||
print(f"Database Name: {config.database.database}")
|
||||
print(f"Database User: {config.database.username}")
|
||||
print(f"Log Level: {config.server.log_level}")
|
||||
print(f"Log File: {config.server.log_file}")
|
||||
print(f"Default Session Limit: {config.server.default_session_limit}s ({config.server.default_session_limit//3600}h)")
|
||||
print(f"Max Session Limit: {config.server.max_session_limit}s ({config.server.max_session_limit//3600}h)")
|
||||
print("=" * 50)
|
||||
|
||||
from util.client.generate_client import generate_client_config
|
||||
|
||||
|
||||
def run_gen_client(username: str):
|
||||
"""Generate a client .ovpn file."""
|
||||
generate_client_config(username)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point with command-line interface."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="VPN Access Server - OpenVPN authentication and session management",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s auth # Run authentication (for OpenVPN)
|
||||
%(prog)s session # Run session management (for OpenVPN)
|
||||
%(prog)s init-db # Initialize database schema
|
||||
%(prog)s seed-data # Add sample data for testing
|
||||
%(prog)s test # Run unit tests
|
||||
%(prog)s health-check # Check system health
|
||||
%(prog)s status # Show configuration status
|
||||
%(prog)s gen-client <user> # Generate a client .ovpn file
|
||||
|
||||
Environment Variables:
|
||||
DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
|
||||
LOG_LEVEL, DEFAULT_SESSION_LIMIT, MAX_SESSION_LIMIT
|
||||
|
||||
See .env.example for full configuration options.
|
||||
"""
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
||||
|
||||
subparsers.add_parser('auth', help='Run authentication (for OpenVPN)')
|
||||
subparsers.add_parser('session', help='Run session management (for OpenVPN)')
|
||||
subparsers.add_parser('init-db', help='Initialize database schema')
|
||||
subparsers.add_parser('seed-data', help='Add sample data for testing')
|
||||
subparsers.add_parser('test', help='Run unit tests')
|
||||
subparsers.add_parser('health-check', help='Check system health')
|
||||
subparsers.add_parser('status', help='Show configuration status')
|
||||
|
||||
gen_client_parser = subparsers.add_parser('gen-client', help='Generate a client .ovpn file')
|
||||
gen_client_parser.add_argument('username', help='Username for the client config')
|
||||
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
action='version',
|
||||
version='VPN Access Server 1.0.0'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.command == 'auth':
|
||||
run_auth()
|
||||
elif args.command == 'session':
|
||||
run_session()
|
||||
elif args.command == 'init-db':
|
||||
run_init_db()
|
||||
elif args.command == 'seed-data':
|
||||
run_seed_data()
|
||||
elif args.command == 'test':
|
||||
run_tests()
|
||||
elif args.command == 'health-check':
|
||||
if not health_check():
|
||||
sys.exit(1)
|
||||
elif args.command == 'status':
|
||||
show_status()
|
||||
elif args.command == 'gen-client':
|
||||
run_gen_client(args.username)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ Operation cancelled by user")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
217
main.py
Executable file → Normal file
217
main.py
Executable file → Normal file
@ -1,192 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
VPN Access Server - Main Entry Point
|
||||
|
||||
Unified command-line interface for all VPN Access Server operations.
|
||||
Supports authentication, session management, database operations, and testing.
|
||||
FastAPI server for the VPN Access Server.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import uvicorn
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add the access module to the Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'access'))
|
||||
|
||||
def run_auth():
|
||||
"""Run authentication module."""
|
||||
from access.auth import main as auth_main
|
||||
auth_main()
|
||||
|
||||
def run_session():
|
||||
"""Run session management module."""
|
||||
from access.session import main as session_main
|
||||
session_main()
|
||||
|
||||
def run_init_db():
|
||||
"""Initialize database schema."""
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts'))
|
||||
from scripts.init_db import main as init_db_main
|
||||
init_db_main()
|
||||
|
||||
def run_seed_data():
|
||||
"""Seed database with sample data."""
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts'))
|
||||
from scripts.seed_data import main as seed_data_main
|
||||
seed_data_main()
|
||||
|
||||
def run_tests():
|
||||
"""Run unit tests."""
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run([
|
||||
sys.executable, '-m', 'pytest', 'tests/', '-v'
|
||||
], cwd=os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.exit(result.returncode)
|
||||
except FileNotFoundError:
|
||||
print("pytest not found. Install test dependencies with: uv add --group test pytest pytest-cov")
|
||||
sys.exit(1)
|
||||
|
||||
def health_check():
|
||||
"""Check system health and connectivity."""
|
||||
from access.config import config
|
||||
from access.db import db
|
||||
from access.utils import setup_logging
|
||||
|
||||
logger = setup_logging(log_level='INFO')
|
||||
|
||||
print("🔍 VPN Access Server Health Check")
|
||||
print("=" * 50)
|
||||
|
||||
# Configuration check
|
||||
print("📋 Configuration...")
|
||||
if config.validate():
|
||||
print("✅ Configuration is valid")
|
||||
else:
|
||||
print("❌ Configuration validation failed")
|
||||
return False
|
||||
|
||||
# Database check
|
||||
print("🗄️ Database connectivity...")
|
||||
if db.health_check():
|
||||
print("✅ Database connection successful")
|
||||
else:
|
||||
print("❌ Database connection failed")
|
||||
return False
|
||||
|
||||
# Test utilities
|
||||
print("🔧 Utility functions...")
|
||||
from access.utils import validate_mac_address, normalize_mac_address
|
||||
test_mac = "00:11:22:33:44:55"
|
||||
if validate_mac_address(test_mac) and normalize_mac_address("001122334455") == test_mac:
|
||||
print("✅ Utility functions working")
|
||||
else:
|
||||
print("❌ Utility functions failed")
|
||||
return False
|
||||
|
||||
print("=" * 50)
|
||||
print("✅ All health checks passed!")
|
||||
return True
|
||||
|
||||
def show_status():
|
||||
"""Show system status and configuration."""
|
||||
from access.config import config
|
||||
|
||||
print("📊 VPN Access Server Status")
|
||||
print("=" * 50)
|
||||
print(f"Database Host: {config.database.host}:{config.database.port}")
|
||||
print(f"Database Name: {config.database.database}")
|
||||
print(f"Database User: {config.database.username}")
|
||||
print(f"Log Level: {config.server.log_level}")
|
||||
print(f"Log File: {config.server.log_file}")
|
||||
print(f"Default Session Limit: {config.server.default_session_limit}s ({config.server.default_session_limit//3600}h)")
|
||||
print(f"Max Session Limit: {config.server.max_session_limit}s ({config.server.max_session_limit//3600}h)")
|
||||
print("=" * 50)
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from util.client.generate_client import generate_client_config
|
||||
|
||||
app = FastAPI(
|
||||
title="VPN Access Server API",
|
||||
description="API for managing VPN clients and server operations.",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
def run_gen_client(username: str):
|
||||
"""Generate a client .ovpn file."""
|
||||
generate_client_config(username)
|
||||
class ClientRequest(BaseModel):
|
||||
username: str
|
||||
email: str
|
||||
|
||||
@app.post("/api/client/generate", summary="Generate a new VPN client configuration")
|
||||
def generate_client(request: ClientRequest):
|
||||
"""
|
||||
Generates a new OpenVPN client configuration (.ovpn) file.
|
||||
|
||||
def main():
|
||||
"""Main entry point with command-line interface."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="VPN Access Server - OpenVPN authentication and session management",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s auth # Run authentication (for OpenVPN)
|
||||
%(prog)s session # Run session management (for OpenVPN)
|
||||
%(prog)s init-db # Initialize database schema
|
||||
%(prog)s seed-data # Add sample data for testing
|
||||
%(prog)s test # Run unit tests
|
||||
%(prog)s health-check # Check system health
|
||||
%(prog)s status # Show configuration status
|
||||
%(prog)s gen-client <user> # Generate a client .ovpn file
|
||||
|
||||
Environment Variables:
|
||||
DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
|
||||
LOG_LEVEL, DEFAULT_SESSION_LIMIT, MAX_SESSION_LIMIT
|
||||
|
||||
See .env.example for full configuration options.
|
||||
"""
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
||||
|
||||
subparsers.add_parser('auth', help='Run authentication (for OpenVPN)')
|
||||
subparsers.add_parser('session', help='Run session management (for OpenVPN)')
|
||||
subparsers.add_parser('init-db', help='Initialize database schema')
|
||||
subparsers.add_parser('seed-data', help='Add sample data for testing')
|
||||
subparsers.add_parser('test', help='Run unit tests')
|
||||
subparsers.add_parser('health-check', help='Check system health')
|
||||
subparsers.add_parser('status', help='Show configuration status')
|
||||
|
||||
gen_client_parser = subparsers.add_parser('gen-client', help='Generate a client .ovpn file')
|
||||
gen_client_parser.add_argument('username', help='Username for the client config')
|
||||
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
action='version',
|
||||
version='VPN Access Server 1.0.0'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.command == 'auth':
|
||||
run_auth()
|
||||
elif args.command == 'session':
|
||||
run_session()
|
||||
elif args.command == 'init-db':
|
||||
run_init_db()
|
||||
elif args.command == 'seed-data':
|
||||
run_seed_data()
|
||||
elif args.command == 'test':
|
||||
run_tests()
|
||||
elif args.command == 'health-check':
|
||||
if not health_check():
|
||||
sys.exit(1)
|
||||
elif args.command == 'status':
|
||||
show_status()
|
||||
elif args.command == 'gen-client':
|
||||
run_gen_client(args.username)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ Operation cancelled by user")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
This endpoint will:
|
||||
1. Trigger `easyrsa` to generate a new client certificate and key.
|
||||
2. Assemble the `.ovpn` file with the new certificate/key and the server's CA and TA keys.
|
||||
3. Save the file to the server's client configuration directory.
|
||||
"""
|
||||
success, message = generate_client_config(request.username)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail=message)
|
||||
return {"message": message}
|
||||
|
||||
@app.get("/api/client/get-config/{username}", summary="Download a client configuration file")
|
||||
def get_client_config(username: str, email: str):
|
||||
"""
|
||||
Downloads the .ovpn configuration file for a specific client.
|
||||
The file is sought in the `generated-clients` directory.
|
||||
"""
|
||||
file_path = os.path.join("generated-clients", f"{username}.ovpn")
|
||||
if not os.path.isfile(file_path):
|
||||
raise HTTPException(status_code=404, detail="Configuration file not found for this user. Please generate it first.")
|
||||
|
||||
return FileResponse(path=file_path, filename=f"{username}.ovpn", media_type='application/octet-stream')
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
uvicorn.run(app, host="0.0.0.0", port=8443)
|
||||
|
||||
@ -8,6 +8,8 @@ dependencies = [
|
||||
"db>=0.1.1",
|
||||
"mysql-connector-python>=8.0.33",
|
||||
"utils>=1.0.2",
|
||||
"fastapi>=0.111.0",
|
||||
"uvicorn>=0.20.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
@ -17,7 +19,7 @@ test = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
vpn-access-server = "main:main"
|
||||
vpn-access-server = "cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
@ -4,48 +4,50 @@ Utility for generating OpenVPN client configuration files.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from typing import Tuple
|
||||
|
||||
def generate_client_config(username: str):
|
||||
def generate_client_config(username: str, email: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Generates a .ovpn file for a given user.
|
||||
|
||||
Args:
|
||||
username: The username for which to generate the config.
|
||||
email: The email for which to generate the config and used as password
|
||||
|
||||
Returns:
|
||||
A tuple containing a boolean indicating success and a message.
|
||||
"""
|
||||
easyrsa_dir = "/home/arthur/openvpn-ca/"
|
||||
ca_path = "/home/arthur/openvpn-ca/pki/ca.crt"
|
||||
ta_path = "/home/arthur/openvpn-ca/ta.key"
|
||||
client_crt_path = f"/home/arthur/openvpn-ca/pki/issued/{username}.crt"
|
||||
client_key_path = f"/home/arthur/openvpn-ca/pki/private/{username}.key"
|
||||
output_path = f"/etc/openvpn/client/{username}.ovpn"
|
||||
output_path = f"generated-clients/{username}.ovpn"
|
||||
|
||||
# config password into env
|
||||
password = email
|
||||
env = os.environ.copy()
|
||||
env["EASYRSA_PASSOUT"] = f"pass:{password}"
|
||||
# Step 1: Generate the client certificate
|
||||
print(f"Generating certificate for user: {username}...")
|
||||
try:
|
||||
command = ["./easyrsa", "--batch", "build-client-full", username, "nopass"]
|
||||
command = ["./easyrsa", "--batch", "build-client-full", username]
|
||||
process = subprocess.run(
|
||||
command,
|
||||
cwd=easyrsa_dir,
|
||||
env=env,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(process.stdout)
|
||||
print("Certificate generated successfully.")
|
||||
except FileNotFoundError:
|
||||
print(f"Error: 'easyrsa' script not found in {easyrsa_dir}. Please check the path.")
|
||||
return
|
||||
return False, f"Error: 'easyrsa' script not found in {easyrsa_dir}. Please check the path."
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error generating certificate for user: {username}")
|
||||
print(f"Return code: {e.returncode}")
|
||||
print(f"Stderr: {e.stderr}")
|
||||
return
|
||||
return False, f"Error generating certificate for user: {username}. Stderr: {e.stderr}"
|
||||
|
||||
# Step 2: Verify that all required files exist
|
||||
for f in [ca_path, ta_path, client_crt_path, client_key_path]:
|
||||
if not os.path.isfile(f):
|
||||
print(f"Error: Cannot read file '{f}'. File not found after generation.")
|
||||
return
|
||||
return False, f"Error: Cannot read file '{f}'. File not found after generation."
|
||||
|
||||
# Step 3: Read the content of the files
|
||||
try:
|
||||
@ -58,8 +60,7 @@ def generate_client_config(username: str):
|
||||
with open(ta_path, 'r') as f:
|
||||
ta_content = f.read()
|
||||
except IOError as e:
|
||||
print(f"Error reading files: {e}")
|
||||
return
|
||||
return False, f"Error reading files: {e}"
|
||||
|
||||
# Step 4: Assemble the .ovpn configuration
|
||||
ovpn_config = f"""
|
||||
@ -98,22 +99,17 @@ key-direction 1
|
||||
# Step 5: Write the configuration to the output file
|
||||
try:
|
||||
output_dir = os.path.dirname(output_path)
|
||||
# Check if dir exists and if we have write permission
|
||||
if not os.path.isdir(output_dir) or not os.access(output_dir, os.W_OK):
|
||||
print(f"Error: Output directory '{output_dir}' does not exist or is not writable.")
|
||||
print("Please ensure you have the correct permissions to write to this directory.")
|
||||
# As a fallback, save to a local directory
|
||||
local_output_dir = "generated-clients"
|
||||
if not os.path.exists(local_output_dir):
|
||||
os.makedirs(local_output_dir)
|
||||
local_output_path = os.path.join(local_output_dir, f"{username}.ovpn")
|
||||
with open(local_output_path, 'w') as f:
|
||||
f.write(ovpn_config)
|
||||
print(f"Could not write to server path. Saved config locally to: {local_output_path}")
|
||||
return
|
||||
return True, f"Successfully generated client config. Could not write to server path. Saved config locally to: {local_output_path}"
|
||||
|
||||
with open(output_path, 'w') as f:
|
||||
f.write(ovpn_config)
|
||||
print(f"Successfully generated client config: {output_path}")
|
||||
return True, f"Successfully generated client config: {output_path}"
|
||||
except IOError as e:
|
||||
print(f"Error writing to file: {e}")
|
||||
return False, f"Error writing to file: {e}"
|
||||
|
||||
189
uv.lock
generated
189
uv.lock
generated
@ -2,12 +2,46 @@ version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "antiorm"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/f8/71baa4824d9666c1be51d117119579a97f461ddbded48b2e01a6ad0554b5/antiorm-1.2.1.tar.gz", hash = "sha256:96eb1841ce5163db4cf1dc13f4499ec2d7cffc190cf724b78ffdd3e6b7c4ff93", size = 171953, upload-time = "2016-06-28T22:52:03.354Z" }
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@ -87,6 +121,38 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/22/f65d64c83e63790b3273c6adb3bff338ad594f46d84b41bd1f94593b40a6/db-0.1.1.tar.gz", hash = "sha256:980e772f15c1161d3b287ffec4f144e40961b0b3e6d5102809577870bf6c5808", size = 3350, upload-time = "2014-12-13T05:13:58.159Z" }
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.119.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7", size = 336756, upload-time = "2025-10-11T17:13:40.53Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2", size = 107095, upload-time = "2025-10-11T17:13:39.048Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
@ -128,6 +194,70 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.12.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.41.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
@ -167,20 +297,77 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.48.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utils"
|
||||
version = "1.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/1f/c196d21c2df061923154aecf24cab049a114394956e90c9bfbfdd398e27a/utils-1.0.2.tar.gz", hash = "sha256:f4d5157e27e9d434006b5b52a1ec951a34e53e7ecaa145d43a153ec452eb5d9e", size = 13203, upload-time = "2024-01-06T07:11:25.75Z" }
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.38.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vpn-access-server"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "db" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "mysql-connector-python" },
|
||||
{ name = "utils" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@ -198,10 +385,12 @@ test = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "db", specifier = ">=0.1.1" },
|
||||
{ name = "fastapi", specifier = ">=0.111.0" },
|
||||
{ name = "mysql-connector-python", specifier = ">=8.0.33" },
|
||||
{ name = "pytest", marker = "extra == 'test'", specifier = ">=7.0.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'test'", specifier = ">=4.0.0" },
|
||||
{ name = "utils", specifier = ">=1.0.2" },
|
||||
{ name = "uvicorn", specifier = ">=0.20.0" },
|
||||
]
|
||||
provides-extras = ["test"]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user