feat: backend FastAPI funcional con auth, ETA y simulador
This commit is contained in:
BIN
backend/__pycache__/auth.cpython-312.pyc
Normal file
BIN
backend/__pycache__/auth.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/database.cpython-312.pyc
Normal file
BIN
backend/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/main.cpython-312.pyc
Normal file
BIN
backend/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/models.cpython-312.pyc
Normal file
BIN
backend/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/schemas.cpython-312.pyc
Normal file
BIN
backend/__pycache__/schemas.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/simulator.cpython-312.pyc
Normal file
BIN
backend/__pycache__/simulator.cpython-312.pyc
Normal file
Binary file not shown.
40
backend/auth.py
Normal file
40
backend/auth.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from database import get_db
|
||||||
|
import models
|
||||||
|
|
||||||
|
SECRET_KEY = "hackonlinces2026supersecretkey"
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
||||||
|
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
|
||||||
|
|
||||||
|
def hash_password(password: str):
|
||||||
|
return pwd_context.hash(password)
|
||||||
|
|
||||||
|
def verify_password(plain: str, hashed: str):
|
||||||
|
return pwd_context.verify(plain, hashed)
|
||||||
|
|
||||||
|
def create_token(data: dict):
|
||||||
|
to_encode = data.copy()
|
||||||
|
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
|
||||||
|
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
email = payload.get("sub")
|
||||||
|
if email is None:
|
||||||
|
raise HTTPException(status_code=401, detail="Token inválido")
|
||||||
|
except JWTError:
|
||||||
|
raise HTTPException(status_code=401, detail="Token inválido")
|
||||||
|
user = db.query(models.Usuario).filter(models.Usuario.email == email).first()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=401, detail="Usuario no encontrado")
|
||||||
|
return user
|
||||||
9
backend/data/colonias-rutas.json
Normal file
9
backend/data/colonias-rutas.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{ "colonia": "Zona Centro", "routeId": "RUTA-01", "horarioEstimado": "Matutino (06:30 - 07:15)" },
|
||||||
|
{ "colonia": "Las Arboledas", "routeId": "RUTA-01", "horarioEstimado": "Matutino (07:00 - 07:30)" },
|
||||||
|
{ "colonia": "Trojes", "routeId": "RUTA-13", "horarioEstimado": "Matutino (06:40 - 07:10)" },
|
||||||
|
{ "colonia": "San Juanico", "routeId": "RUTA-03", "horarioEstimado": "Matutino (06:45 - 07:15)" },
|
||||||
|
{ "colonia": "Los Olivos", "routeId": "RUTA-04", "horarioEstimado": "Matutino (07:00 - 07:40)" },
|
||||||
|
{ "colonia": "Rancho Seco", "routeId": "RUTA-05", "horarioEstimado": "Vespertino (14:15 - 15:00)" },
|
||||||
|
{ "colonia": "Las Insurgentes", "routeId": "RUTA-12", "horarioEstimado": "Matutino (06:35 - 07:10)" }
|
||||||
|
]
|
||||||
26
backend/data/notificaciones.json
Normal file
26
backend/data/notificaciones.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"triggerEvent": "ROUTE_START",
|
||||||
|
"condition": "Cuando positionId cambia de 1 a 2",
|
||||||
|
"pushPayload": {
|
||||||
|
"title": "¡Ruta Iniciada!",
|
||||||
|
"body": "El camión recolector ha salido del Relleno Sanitario rumbo a tu sector. Asegúrate de tener listos tus residuos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"triggerEvent": "TRUCK_PROXIMITY",
|
||||||
|
"condition": "Cuando positionId llega a 4 (punto previo al destino)",
|
||||||
|
"pushPayload": {
|
||||||
|
"title": "Camión Cercano",
|
||||||
|
"body": "El camión está a menos de 15 minutos de tu domicilio. Es momento de sacar tus bolsas a la acera."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"triggerEvent": "ROUTE_COMPLETED",
|
||||||
|
"condition": "Cuando positionId llega a 8 (retorno al basurero)",
|
||||||
|
"pushPayload": {
|
||||||
|
"title": "Servicio Finalizado",
|
||||||
|
"body": "El camión de tu sector ha concluido su jornada de recolección diaria."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
backend/data/rutas.json
Normal file
BIN
backend/data/rutas.json
Normal file
Binary file not shown.
16
backend/database.py
Normal file
16
backend/database.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/hackonlinces"
|
||||||
|
|
||||||
|
engine = create_engine(DATABASE_URL)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
77
backend/main.py
Normal file
77
backend/main.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from fastapi import FastAPI, Depends, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
from database import engine, get_db
|
||||||
|
import models, schemas, auth, simulator
|
||||||
|
|
||||||
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
app = FastAPI(title="HackOnLinces 2026 - Recolección de Residuos")
|
||||||
|
|
||||||
|
app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
||||||
|
allow_methods=["*"], allow_headers=["*"])
|
||||||
|
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
scheduler.add_job(simulator.avanzar_rutas, "interval", minutes=2)
|
||||||
|
scheduler.start()
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup():
|
||||||
|
db = next(get_db())
|
||||||
|
simulator.init_rutas(db)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
return {"message": "API HackOnLinces 2026 funcionando"}
|
||||||
|
|
||||||
|
@app.post("/auth/register", response_model=schemas.Token)
|
||||||
|
def register(user: schemas.UsuarioCreate, db: Session = Depends(get_db)):
|
||||||
|
if db.query(models.Usuario).filter_by(email=user.email).first():
|
||||||
|
raise HTTPException(status_code=400, detail="Email ya registrado")
|
||||||
|
nuevo = models.Usuario(email=user.email,
|
||||||
|
hashed_password=auth.hash_password(user.password))
|
||||||
|
db.add(nuevo)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(nuevo)
|
||||||
|
token = auth.create_token({"sub": nuevo.email})
|
||||||
|
return {"access_token": token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
@app.post("/auth/login", response_model=schemas.Token)
|
||||||
|
def login(user: schemas.UsuarioLogin, db: Session = Depends(get_db)):
|
||||||
|
db_user = db.query(models.Usuario).filter_by(email=user.email).first()
|
||||||
|
if not db_user or not auth.verify_password(user.password, db_user.hashed_password):
|
||||||
|
raise HTTPException(status_code=401, detail="Credenciales incorrectas")
|
||||||
|
token = auth.create_token({"sub": db_user.email})
|
||||||
|
return {"access_token": token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
@app.post("/domicilios", response_model=schemas.DomicilioResponse)
|
||||||
|
def crear_domicilio(data: schemas.DomicilioCreate,
|
||||||
|
current_user=Depends(auth.get_current_user),
|
||||||
|
db: Session = Depends(get_db)):
|
||||||
|
colonia_key = data.colonia.lower()
|
||||||
|
colonia_info = simulator.COLONIAS.get(colonia_key)
|
||||||
|
if not colonia_info:
|
||||||
|
raise HTTPException(status_code=404, detail="Colonia no encontrada en el sistema")
|
||||||
|
dom = models.Domicilio(direccion=data.direccion, colonia=data.colonia,
|
||||||
|
lat=data.lat, lng=data.lng,
|
||||||
|
route_id=colonia_info["routeId"],
|
||||||
|
usuario_id=current_user.id)
|
||||||
|
db.add(dom)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(dom)
|
||||||
|
return dom
|
||||||
|
|
||||||
|
@app.get("/eta/{domicilio_id}", response_model=schemas.ETAResponse)
|
||||||
|
def get_eta(domicilio_id: int,
|
||||||
|
current_user=Depends(auth.get_current_user),
|
||||||
|
db: Session = Depends(get_db)):
|
||||||
|
dom = db.query(models.Domicilio).filter_by(id=domicilio_id).first()
|
||||||
|
if not dom:
|
||||||
|
raise HTTPException(status_code=404, detail="Domicilio no encontrado")
|
||||||
|
if dom.usuario_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="No tienes acceso a este domicilio")
|
||||||
|
eta = simulator.get_eta(dom.route_id, db)
|
||||||
|
if not eta:
|
||||||
|
raise HTTPException(status_code=404, detail="Ruta no encontrada")
|
||||||
|
return {**eta, "route_id": dom.route_id, "colonia": dom.colonia}
|
||||||
30
backend/models.py
Normal file
30
backend/models.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, Float, ForeignKey, DateTime
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from database import Base
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class Usuario(Base):
|
||||||
|
__tablename__ = "usuarios"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
email = Column(String, unique=True, index=True)
|
||||||
|
hashed_password = Column(String)
|
||||||
|
domicilios = relationship("Domicilio", back_populates="usuario")
|
||||||
|
|
||||||
|
class Domicilio(Base):
|
||||||
|
__tablename__ = "domicilios"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
direccion = Column(String)
|
||||||
|
colonia = Column(String)
|
||||||
|
lat = Column(Float)
|
||||||
|
lng = Column(Float)
|
||||||
|
route_id = Column(String)
|
||||||
|
usuario_id = Column(Integer, ForeignKey("usuarios.id"))
|
||||||
|
usuario = relationship("Usuario", back_populates="domicilios")
|
||||||
|
|
||||||
|
class EstadoRuta(Base):
|
||||||
|
__tablename__ = "estado_rutas"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
route_id = Column(String, unique=True, index=True)
|
||||||
|
current_position_id = Column(Integer, default=1)
|
||||||
|
status = Column(String, default="EN_RUTA")
|
||||||
|
updated_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
28
backend/requirements.txt
Normal file
28
backend/requirements.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
annotated-doc==0.0.4
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.13.0
|
||||||
|
APScheduler==3.11.2
|
||||||
|
bcrypt==5.0.0
|
||||||
|
cffi==2.0.0
|
||||||
|
click==8.4.1
|
||||||
|
cryptography==48.0.0
|
||||||
|
ecdsa==0.19.2
|
||||||
|
fastapi==0.136.1
|
||||||
|
greenlet==3.5.1
|
||||||
|
h11==0.16.0
|
||||||
|
idna==3.16
|
||||||
|
passlib==1.7.4
|
||||||
|
psycopg2-binary==2.9.12
|
||||||
|
pyasn1==0.6.3
|
||||||
|
pycparser==3.0
|
||||||
|
pydantic==2.13.4
|
||||||
|
pydantic_core==2.46.4
|
||||||
|
python-jose==3.5.0
|
||||||
|
rsa==4.9.1
|
||||||
|
six==1.17.0
|
||||||
|
SQLAlchemy==2.0.49
|
||||||
|
starlette==1.0.1
|
||||||
|
typing-inspection==0.4.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
tzlocal==5.3.1
|
||||||
|
uvicorn==0.47.0
|
||||||
37
backend/schemas.py
Normal file
37
backend/schemas.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UsuarioCreate(BaseModel):
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
class UsuarioLogin(BaseModel):
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
class Token(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
|
||||||
|
class DomicilioCreate(BaseModel):
|
||||||
|
direccion: str
|
||||||
|
colonia: str
|
||||||
|
lat: float
|
||||||
|
lng: float
|
||||||
|
|
||||||
|
class DomicilioResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
direccion: str
|
||||||
|
colonia: str
|
||||||
|
route_id: str
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class ETAResponse(BaseModel):
|
||||||
|
route_id: str
|
||||||
|
colonia: str
|
||||||
|
current_position: int
|
||||||
|
mensaje: str
|
||||||
|
ventana_inicio: str
|
||||||
|
ventana_fin: str
|
||||||
|
evento: str
|
||||||
67
backend/simulator.py
Normal file
67
backend/simulator.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from database import SessionLocal
|
||||||
|
import models
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
with open(os.path.join(BASE_DIR, "data", "rutas.json")) as f:
|
||||||
|
RUTAS = json.load(f)
|
||||||
|
|
||||||
|
COLONIAS = {}
|
||||||
|
with open(os.path.join(BASE_DIR, "data", "colonias-rutas.json")) as f:
|
||||||
|
for item in json.load(f):
|
||||||
|
COLONIAS[item["colonia"].lower()] = item
|
||||||
|
|
||||||
|
def init_rutas(db: Session):
|
||||||
|
for ruta in RUTAS:
|
||||||
|
existe = db.query(models.EstadoRuta).filter_by(route_id=ruta["routeId"]).first()
|
||||||
|
if not existe:
|
||||||
|
db.add(models.EstadoRuta(route_id=ruta["routeId"], current_position_id=1))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def avanzar_rutas():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
estados = db.query(models.EstadoRuta).all()
|
||||||
|
for estado in estados:
|
||||||
|
if estado.current_position_id < 8:
|
||||||
|
estado.current_position_id += 1
|
||||||
|
estado.updated_at = datetime.datetime.utcnow()
|
||||||
|
print(f"[SIM] {estado.route_id} → posición {estado.current_position_id}")
|
||||||
|
else:
|
||||||
|
estado.current_position_id = 1
|
||||||
|
db.commit()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def get_eta(route_id: str, db: Session):
|
||||||
|
estado = db.query(models.EstadoRuta).filter_by(route_id=route_id).first()
|
||||||
|
if not estado:
|
||||||
|
return None
|
||||||
|
ruta = next((r for r in RUTAS if r["routeId"] == route_id), None)
|
||||||
|
if not ruta:
|
||||||
|
return None
|
||||||
|
pos = estado.current_position_id
|
||||||
|
if pos >= 8:
|
||||||
|
return {"mensaje": "El servicio de hoy ha finalizado.", "evento": "ROUTE_COMPLETED",
|
||||||
|
"ventana_inicio": "--", "ventana_fin": "--"}
|
||||||
|
pos_actual = ruta["positions"][pos - 1]
|
||||||
|
pos_siguiente = ruta["positions"][min(pos, 7)]
|
||||||
|
ts = datetime.datetime.fromisoformat(pos_siguiente["timestamp"].replace("Z", "+00:00"))
|
||||||
|
ventana_fin = ts.strftime("%H:%M")
|
||||||
|
ventana_inicio = (ts - datetime.timedelta(minutes=7)).strftime("%H:%M")
|
||||||
|
if pos == 1:
|
||||||
|
evento = "ROUTE_START"
|
||||||
|
mensaje = "El camión está por iniciar su recorrido. Ten listos tus residuos."
|
||||||
|
elif pos == 4:
|
||||||
|
evento = "TRUCK_PROXIMITY"
|
||||||
|
mensaje = f"El camión está a menos de 15 minutos. Saca tus bolsas a la acera."
|
||||||
|
else:
|
||||||
|
evento = "EN_CAMINO"
|
||||||
|
mensaje = f"El camión llegará a tu zona entre las {ventana_inicio} y {ventana_fin}."
|
||||||
|
return {"mensaje": mensaje, "evento": evento,
|
||||||
|
"ventana_inicio": ventana_inicio, "ventana_fin": ventana_fin,
|
||||||
|
"current_position": pos}
|
||||||
247
backend/venv/bin/Activate.ps1
Normal file
247
backend/venv/bin/Activate.ps1
Normal file
@@ -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"
|
||||||
70
backend/venv/bin/activate
Normal file
70
backend/venv/bin/activate
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# You cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
unset VIRTUAL_ENV_PROMPT
|
||||||
|
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||||
|
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||||
|
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||||
|
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||||
|
export VIRTUAL_ENV=$(cygpath /home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv)
|
||||||
|
else
|
||||||
|
# use the path as-is
|
||||||
|
export VIRTUAL_ENV=/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
PS1='(venv) '"${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
VIRTUAL_ENV_PROMPT='(venv) '
|
||||||
|
export VIRTUAL_ENV_PROMPT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
27
backend/venv/bin/activate.csh
Normal file
27
backend/venv/bin/activate.csh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 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/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
set prompt = '(venv) '"$prompt"
|
||||||
|
setenv VIRTUAL_ENV_PROMPT '(venv) '
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
69
backend/venv/bin/activate.fish
Normal file
69
backend/venv/bin/activate.fish
Normal file
@@ -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/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# With the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command.
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
# Output the original/"old" prompt.
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
set -gx VIRTUAL_ENV_PROMPT '(venv) '
|
||||||
|
end
|
||||||
8
backend/venv/bin/fastapi
Executable file
8
backend/venv/bin/fastapi
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from fastapi.cli import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
backend/venv/bin/idna
Executable file
8
backend/venv/bin/idna
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from idna.cli import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
backend/venv/bin/pip
Executable file
8
backend/venv/bin/pip
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/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())
|
||||||
8
backend/venv/bin/pip3
Executable file
8
backend/venv/bin/pip3
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/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())
|
||||||
8
backend/venv/bin/pip3.12
Executable file
8
backend/venv/bin/pip3.12
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/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())
|
||||||
8
backend/venv/bin/pyrsa-decrypt
Executable file
8
backend/venv/bin/pyrsa-decrypt
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.cli import decrypt
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(decrypt())
|
||||||
8
backend/venv/bin/pyrsa-encrypt
Executable file
8
backend/venv/bin/pyrsa-encrypt
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.cli import encrypt
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(encrypt())
|
||||||
8
backend/venv/bin/pyrsa-keygen
Executable file
8
backend/venv/bin/pyrsa-keygen
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.cli import keygen
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(keygen())
|
||||||
8
backend/venv/bin/pyrsa-priv2pub
Executable file
8
backend/venv/bin/pyrsa-priv2pub
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.util import private_to_public
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(private_to_public())
|
||||||
8
backend/venv/bin/pyrsa-sign
Executable file
8
backend/venv/bin/pyrsa-sign
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.cli import sign
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(sign())
|
||||||
8
backend/venv/bin/pyrsa-verify
Executable file
8
backend/venv/bin/pyrsa-verify
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from rsa.cli import verify
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(verify())
|
||||||
1
backend/venv/bin/python
Symbolic link
1
backend/venv/bin/python
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
1
backend/venv/bin/python3
Symbolic link
1
backend/venv/bin/python3
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/usr/bin/python3
|
||||||
1
backend/venv/bin/python3.12
Symbolic link
1
backend/venv/bin/python3.12
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
8
backend/venv/bin/uvicorn
Executable file
8
backend/venv/bin/uvicorn
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/x/hackathon-fenix-dd4242d3e4a44627bef70ed13cc9269d/backend/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from uvicorn.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
164
backend/venv/include/site/python3.12/greenlet/greenlet.h
Normal file
164
backend/venv/include/site/python3.12/greenlet/greenlet.h
Normal file
@@ -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 */
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: annotated-doc
|
||||||
|
Version: 0.0.4
|
||||||
|
Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
|
||||||
|
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
||||||
|
License-Expression: MIT
|
||||||
|
License-File: LICENSE
|
||||||
|
Classifier: Intended Audience :: Information Technology
|
||||||
|
Classifier: Intended Audience :: System Administrators
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Topic :: Software Development :: Libraries
|
||||||
|
Classifier: Topic :: Software Development
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Programming Language :: Python :: 3 :: Only
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
|
Classifier: Programming Language :: Python :: 3.14
|
||||||
|
Project-URL: Homepage, https://github.com/fastapi/annotated-doc
|
||||||
|
Project-URL: Documentation, https://github.com/fastapi/annotated-doc
|
||||||
|
Project-URL: Repository, https://github.com/fastapi/annotated-doc
|
||||||
|
Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
|
||||||
|
Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
# Annotated Doc
|
||||||
|
|
||||||
|
Document parameters, class attributes, return types, and variables inline, with `Annotated`.
|
||||||
|
|
||||||
|
<a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
|
||||||
|
<img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
|
||||||
|
</a>
|
||||||
|
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
|
||||||
|
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
|
||||||
|
</a>
|
||||||
|
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||||
|
<img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||||
|
</a>
|
||||||
|
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||||
|
<img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install annotated-doc
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with `uv`:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
uv add annotated-doc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
|
||||||
|
|
||||||
|
For example, to document a parameter `name` in a function `hi` you could do:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from annotated_doc import Doc
|
||||||
|
|
||||||
|
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||||
|
print(f"Hi, {name}!")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use it to document class attributes:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from annotated_doc import Doc
|
||||||
|
|
||||||
|
class User:
|
||||||
|
name: Annotated[str, Doc("The user's name")]
|
||||||
|
age: Annotated[int, Doc("The user's age")]
|
||||||
|
```
|
||||||
|
|
||||||
|
The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
|
||||||
|
|
||||||
|
## Who Uses This
|
||||||
|
|
||||||
|
`annotated-doc` was made for:
|
||||||
|
|
||||||
|
* [FastAPI](https://fastapi.tiangolo.com/)
|
||||||
|
* [Typer](https://typer.tiangolo.com/)
|
||||||
|
* [SQLModel](https://sqlmodel.tiangolo.com/)
|
||||||
|
* [Asyncer](https://asyncer.tiangolo.com/)
|
||||||
|
|
||||||
|
`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
|
||||||
|
|
||||||
|
## Reasons not to use `annotated-doc`
|
||||||
|
|
||||||
|
You are already comfortable with one of the existing docstring formats, like:
|
||||||
|
|
||||||
|
* Sphinx
|
||||||
|
* numpydoc
|
||||||
|
* Google
|
||||||
|
* Keras
|
||||||
|
|
||||||
|
Your team is already comfortable using them.
|
||||||
|
|
||||||
|
You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
|
||||||
|
|
||||||
|
You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
|
||||||
|
|
||||||
|
## Reasons to use `annotated-doc`
|
||||||
|
|
||||||
|
* No micro-syntax to learn for newcomers, it’s **just Python** syntax.
|
||||||
|
* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
|
||||||
|
* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
|
||||||
|
* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
|
||||||
|
* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
|
||||||
|
* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
|
||||||
|
* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
|
||||||
|
* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
|
||||||
|
* A more formalized way to document other symbols, like type aliases, that could use Annotated.
|
||||||
|
* **Support** for apps using FastAPI, Typer and others.
|
||||||
|
* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
|
||||||
|
|
||||||
|
The conclusion was that this was better done as an external effort, in a third-party library.
|
||||||
|
|
||||||
|
So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the terms of the MIT license.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
|
||||||
|
annotated_doc-0.0.4.dist-info/RECORD,,
|
||||||
|
annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
||||||
|
annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
||||||
|
annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
|
||||||
|
annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
|
||||||
|
annotated_doc/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
annotated_doc/__pycache__/main.cpython-312.pyc,,
|
||||||
|
annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
|
||||||
|
annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: pdm-backend (2.4.5)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[console_scripts]
|
||||||
|
|
||||||
|
[gui_scripts]
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sebastián Ramírez
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .main import Doc as Doc
|
||||||
|
|
||||||
|
__version__ = "0.0.4"
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
|
|||||||
|
class Doc:
|
||||||
|
"""Define the documentation of a type annotation using `Annotated`, to be
|
||||||
|
used in class attributes, function and method parameters, return values,
|
||||||
|
and variables.
|
||||||
|
|
||||||
|
The value should be a positional-only string literal to allow static tools
|
||||||
|
like editors and documentation generators to use it.
|
||||||
|
|
||||||
|
This complements docstrings.
|
||||||
|
|
||||||
|
The string value passed is available in the attribute `documentation`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from typing import Annotated
|
||||||
|
from annotated_doc import Doc
|
||||||
|
|
||||||
|
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||||
|
print(f"Hi, {name}!")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, documentation: str, /) -> None:
|
||||||
|
self.documentation = documentation
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Doc({self.documentation!r})"
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.documentation)
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, Doc):
|
||||||
|
return NotImplemented
|
||||||
|
return self.documentation == other.documentation
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
Metadata-Version: 2.3
|
||||||
|
Name: annotated-types
|
||||||
|
Version: 0.7.0
|
||||||
|
Summary: Reusable constraint types to use with typing.Annotated
|
||||||
|
Project-URL: Homepage, https://github.com/annotated-types/annotated-types
|
||||||
|
Project-URL: Source, https://github.com/annotated-types/annotated-types
|
||||||
|
Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
|
||||||
|
Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
|
||||||
|
License-File: LICENSE
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Environment :: Console
|
||||||
|
Classifier: Environment :: MacOS X
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: Information Technology
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: POSIX :: Linux
|
||||||
|
Classifier: Operating System :: Unix
|
||||||
|
Classifier: Programming Language :: Python :: 3 :: Only
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
# annotated-types
|
||||||
|
|
||||||
|
[](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
|
||||||
|
[](https://pypi.python.org/pypi/annotated-types)
|
||||||
|
[](https://github.com/annotated-types/annotated-types)
|
||||||
|
[](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
|
||||||
|
|
||||||
|
[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
|
||||||
|
adding context-specific metadata to existing types, and specifies that
|
||||||
|
`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
|
||||||
|
logic for `x`.
|
||||||
|
|
||||||
|
This package provides metadata objects which can be used to represent common
|
||||||
|
constraints such as upper and lower bounds on scalar values and collection sizes,
|
||||||
|
a `Predicate` marker for runtime checks, and
|
||||||
|
descriptions of how we intend these metadata to be interpreted. In some cases,
|
||||||
|
we also note alternative representations which do not require this package.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install annotated-types
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Annotated
|
||||||
|
from annotated_types import Gt, Len, Predicate
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
|
||||||
|
# Invalid: 17, 18, "19", 19.0, ...
|
||||||
|
factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
|
||||||
|
# Invalid: 4, 8, -2, 5.0, "prime", ...
|
||||||
|
|
||||||
|
my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
|
||||||
|
# Invalid: (1, 2), ["abc"], [0] * 20
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
_While `annotated-types` avoids runtime checks for performance, users should not
|
||||||
|
construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
|
||||||
|
Downstream implementors may choose to raise an error, emit a warning, silently ignore
|
||||||
|
a metadata item, etc., if the metadata objects described below are used with an
|
||||||
|
incompatible type - or for any other reason!_
|
||||||
|
|
||||||
|
### Gt, Ge, Lt, Le
|
||||||
|
|
||||||
|
Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
|
||||||
|
dates, times, strings, sets, etc. Note that the boundary value need not be of the
|
||||||
|
same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
|
||||||
|
is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
|
||||||
|
|
||||||
|
We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
|
||||||
|
as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
|
||||||
|
the `annotated-types` package.
|
||||||
|
|
||||||
|
To be explicit, these types have the following meanings:
|
||||||
|
|
||||||
|
* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
|
||||||
|
* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
|
||||||
|
* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
|
||||||
|
* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
|
||||||
|
|
||||||
|
### Interval
|
||||||
|
|
||||||
|
`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
|
||||||
|
metadata object. `None` attributes should be ignored, and non-`None` attributes
|
||||||
|
treated as per the single bounds above.
|
||||||
|
|
||||||
|
### MultipleOf
|
||||||
|
|
||||||
|
`MultipleOf(multiple_of=x)` might be interpreted in two ways:
|
||||||
|
|
||||||
|
1. Python semantics, implying `value % multiple_of == 0`, or
|
||||||
|
2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
|
||||||
|
where `int(value / multiple_of) == value / multiple_of`.
|
||||||
|
|
||||||
|
We encourage users to be aware of these two common interpretations and their
|
||||||
|
distinct behaviours, especially since very large or non-integer numbers make
|
||||||
|
it easy to cause silent data corruption due to floating-point imprecision.
|
||||||
|
|
||||||
|
We encourage libraries to carefully document which interpretation they implement.
|
||||||
|
|
||||||
|
### MinLen, MaxLen, Len
|
||||||
|
|
||||||
|
`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
|
||||||
|
|
||||||
|
As well as `Len()` which can optionally include upper and lower bounds, we also
|
||||||
|
provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
|
||||||
|
and `Len(max_length=y)` respectively.
|
||||||
|
|
||||||
|
`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
|
||||||
|
|
||||||
|
Examples of usage:
|
||||||
|
|
||||||
|
* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
|
||||||
|
* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
|
||||||
|
* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
|
||||||
|
* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
|
||||||
|
* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
|
||||||
|
|
||||||
|
#### Changed in v0.4.0
|
||||||
|
|
||||||
|
* `min_inclusive` has been renamed to `min_length`, no change in meaning
|
||||||
|
* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
|
||||||
|
* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
|
||||||
|
meaning of the upper bound in slices vs. `Len`
|
||||||
|
|
||||||
|
See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
|
||||||
|
|
||||||
|
### Timezone
|
||||||
|
|
||||||
|
`Timezone` can be used with a `datetime` or a `time` to express which timezones
|
||||||
|
are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
|
||||||
|
`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
|
||||||
|
expresses that any timezone-aware datetime is allowed. You may also pass a specific
|
||||||
|
timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
|
||||||
|
object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
|
||||||
|
allow a specific timezone, though we note that this is often a symptom of fragile design.
|
||||||
|
|
||||||
|
#### Changed in v0.x.x
|
||||||
|
|
||||||
|
* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
|
||||||
|
`timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
|
||||||
|
|
||||||
|
### Unit
|
||||||
|
|
||||||
|
`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
|
||||||
|
a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
|
||||||
|
would be a float representing a velocity in meters per second.
|
||||||
|
|
||||||
|
Please note that `annotated_types` itself makes no attempt to parse or validate
|
||||||
|
the unit string in any way. That is left entirely to downstream libraries,
|
||||||
|
such as [`pint`](https://pint.readthedocs.io) or
|
||||||
|
[`astropy.units`](https://docs.astropy.org/en/stable/units/).
|
||||||
|
|
||||||
|
An example of how a library might use this metadata:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from annotated_types import Unit
|
||||||
|
from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
|
||||||
|
|
||||||
|
# given a type annotated with a unit:
|
||||||
|
Meters = Annotated[float, Unit("m")]
|
||||||
|
|
||||||
|
|
||||||
|
# you can cast the annotation to a specific unit type with any
|
||||||
|
# callable that accepts a string and returns the desired type
|
||||||
|
T = TypeVar("T")
|
||||||
|
def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
|
||||||
|
if get_origin(tp) is Annotated:
|
||||||
|
for arg in get_args(tp):
|
||||||
|
if isinstance(arg, Unit):
|
||||||
|
return unit_cls(arg.unit)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# using `pint`
|
||||||
|
import pint
|
||||||
|
pint_unit = cast_unit(Meters, pint.Unit)
|
||||||
|
|
||||||
|
|
||||||
|
# using `astropy.units`
|
||||||
|
import astropy.units as u
|
||||||
|
astropy_unit = cast_unit(Meters, u.Unit)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Predicate
|
||||||
|
|
||||||
|
`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
|
||||||
|
Users should prefer the statically inspectable metadata above, but if you need
|
||||||
|
the full power and flexibility of arbitrary runtime predicates... here it is.
|
||||||
|
|
||||||
|
For some common constraints, we provide generic types:
|
||||||
|
|
||||||
|
* `IsLower = Annotated[T, Predicate(str.islower)]`
|
||||||
|
* `IsUpper = Annotated[T, Predicate(str.isupper)]`
|
||||||
|
* `IsDigit = Annotated[T, Predicate(str.isdigit)]`
|
||||||
|
* `IsFinite = Annotated[T, Predicate(math.isfinite)]`
|
||||||
|
* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
|
||||||
|
* `IsNan = Annotated[T, Predicate(math.isnan)]`
|
||||||
|
* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
|
||||||
|
* `IsInfinite = Annotated[T, Predicate(math.isinf)]`
|
||||||
|
* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
|
||||||
|
|
||||||
|
so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
|
||||||
|
(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
|
||||||
|
|
||||||
|
Some libraries might have special logic to handle known or understandable predicates,
|
||||||
|
for example by checking for `str.isdigit` and using its presence to both call custom
|
||||||
|
logic to enforce digit-only strings, and customise some generated external schema.
|
||||||
|
Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
|
||||||
|
favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
|
||||||
|
|
||||||
|
To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
|
||||||
|
|
||||||
|
We do not specify what behaviour should be expected for predicates that raise
|
||||||
|
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||||
|
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||||
|
and then propagate or discard the resulting
|
||||||
|
`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
|
||||||
|
exception. We encourage libraries to document the behaviour they choose.
|
||||||
|
|
||||||
|
### Doc
|
||||||
|
|
||||||
|
`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
|
||||||
|
|
||||||
|
It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
|
||||||
|
|
||||||
|
It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
|
||||||
|
|
||||||
|
This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
|
||||||
|
|
||||||
|
### Integrating downstream types with `GroupedMetadata`
|
||||||
|
|
||||||
|
Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
|
||||||
|
This can help reduce verbosity and cognitive overhead for users.
|
||||||
|
For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Iterator
|
||||||
|
from annotated_types import GroupedMetadata, Ge
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Field(GroupedMetadata):
|
||||||
|
ge: int | None = None
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]:
|
||||||
|
# Iterating over a GroupedMetadata object should yield annotated-types
|
||||||
|
# constraint metadata objects which describe it as fully as possible,
|
||||||
|
# and may include other unknown objects too.
|
||||||
|
if self.ge is not None:
|
||||||
|
yield Ge(self.ge)
|
||||||
|
if self.description is not None:
|
||||||
|
yield Description(self.description)
|
||||||
|
```
|
||||||
|
|
||||||
|
Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
|
||||||
|
|
||||||
|
Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
|
||||||
|
|
||||||
|
Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
|
||||||
|
|
||||||
|
### Consuming metadata
|
||||||
|
|
||||||
|
We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
|
||||||
|
|
||||||
|
It is up to the implementer to determine how this metadata is used.
|
||||||
|
You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
|
||||||
|
|
||||||
|
## Design & History
|
||||||
|
|
||||||
|
This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
|
||||||
|
and Hypothesis, with the goal of making it as easy as possible for end-users to
|
||||||
|
provide more informative annotations for use by runtime libraries.
|
||||||
|
|
||||||
|
It is deliberately minimal, and following PEP-593 allows considerable downstream
|
||||||
|
discretion in what (if anything!) they choose to support. Nonetheless, we expect
|
||||||
|
that staying simple and covering _only_ the most common use-cases will give users
|
||||||
|
and maintainers the best experience we can. If you'd like more constraints for your
|
||||||
|
types - follow our lead, by defining them and documenting them downstream!
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
|
||||||
|
annotated_types-0.7.0.dist-info/RECORD,,
|
||||||
|
annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
||||||
|
annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
|
||||||
|
annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
|
||||||
|
annotated_types/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
annotated_types/__pycache__/test_cases.cpython-312.pyc,,
|
||||||
|
annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: hatchling 1.24.2
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2022 the contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import tzinfo
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing_extensions import Protocol, runtime_checkable
|
||||||
|
else:
|
||||||
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
from typing_extensions import Annotated, Literal
|
||||||
|
else:
|
||||||
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
EllipsisType = type(Ellipsis)
|
||||||
|
KW_ONLY = {}
|
||||||
|
SLOTS = {}
|
||||||
|
else:
|
||||||
|
from types import EllipsisType
|
||||||
|
|
||||||
|
KW_ONLY = {"kw_only": True}
|
||||||
|
SLOTS = {"slots": True}
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BaseMetadata',
|
||||||
|
'GroupedMetadata',
|
||||||
|
'Gt',
|
||||||
|
'Ge',
|
||||||
|
'Lt',
|
||||||
|
'Le',
|
||||||
|
'Interval',
|
||||||
|
'MultipleOf',
|
||||||
|
'MinLen',
|
||||||
|
'MaxLen',
|
||||||
|
'Len',
|
||||||
|
'Timezone',
|
||||||
|
'Predicate',
|
||||||
|
'LowerCase',
|
||||||
|
'UpperCase',
|
||||||
|
'IsDigits',
|
||||||
|
'IsFinite',
|
||||||
|
'IsNotFinite',
|
||||||
|
'IsNan',
|
||||||
|
'IsNotNan',
|
||||||
|
'IsInfinite',
|
||||||
|
'IsNotInfinite',
|
||||||
|
'doc',
|
||||||
|
'DocInfo',
|
||||||
|
'__version__',
|
||||||
|
)
|
||||||
|
|
||||||
|
__version__ = '0.7.0'
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
# arguments that start with __ are considered
|
||||||
|
# positional only
|
||||||
|
# see https://peps.python.org/pep-0484/#positional-only-arguments
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsGt(Protocol):
|
||||||
|
def __gt__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsGe(Protocol):
|
||||||
|
def __ge__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsLt(Protocol):
|
||||||
|
def __lt__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsLe(Protocol):
|
||||||
|
def __le__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsMod(Protocol):
|
||||||
|
def __mod__(self: T, __other: T) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsDiv(Protocol):
|
||||||
|
def __div__(self: T, __other: T) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMetadata:
|
||||||
|
"""Base class for all metadata.
|
||||||
|
|
||||||
|
This exists mainly so that implementers
|
||||||
|
can do `isinstance(..., BaseMetadata)` while traversing field annotations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Gt(BaseMetadata):
|
||||||
|
"""Gt(gt=x) implies that the value must be greater than x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``>`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gt: SupportsGt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Ge(BaseMetadata):
|
||||||
|
"""Ge(ge=x) implies that the value must be greater than or equal to x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``>=`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ge: SupportsGe
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Lt(BaseMetadata):
|
||||||
|
"""Lt(lt=x) implies that the value must be less than x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``<`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lt: SupportsLt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Le(BaseMetadata):
|
||||||
|
"""Le(le=x) implies that the value must be less than or equal to x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``<=`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
le: SupportsLe
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class GroupedMetadata(Protocol):
|
||||||
|
"""A grouping of multiple objects, like typing.Unpack.
|
||||||
|
|
||||||
|
`GroupedMetadata` on its own is not metadata and has no meaning.
|
||||||
|
All of the constraints and metadata should be fully expressable
|
||||||
|
in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
|
||||||
|
|
||||||
|
Concrete implementations should override `GroupedMetadata.__iter__()`
|
||||||
|
to add their own metadata.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> @dataclass
|
||||||
|
>>> class Field(GroupedMetadata):
|
||||||
|
>>> gt: float | None = None
|
||||||
|
>>> description: str | None = None
|
||||||
|
...
|
||||||
|
>>> def __iter__(self) -> Iterable[object]:
|
||||||
|
>>> if self.gt is not None:
|
||||||
|
>>> yield Gt(self.gt)
|
||||||
|
>>> if self.description is not None:
|
||||||
|
>>> yield Description(self.gt)
|
||||||
|
|
||||||
|
Also see the implementation of `Interval` below for an example.
|
||||||
|
|
||||||
|
Parsers should recognize this and unpack it so that it can be used
|
||||||
|
both with and without unpacking:
|
||||||
|
|
||||||
|
- `Annotated[int, Field(...)]` (parser must unpack Field)
|
||||||
|
- `Annotated[int, *Field(...)]` (PEP-646)
|
||||||
|
""" # noqa: trailing-whitespace
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]:
|
||||||
|
...
|
||||||
|
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
__slots__ = () # allow subclasses to use slots
|
||||||
|
|
||||||
|
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
||||||
|
# Basic ABC like functionality without the complexity of an ABC
|
||||||
|
super().__init_subclass__(*args, **kwargs)
|
||||||
|
if cls.__iter__ is GroupedMetadata.__iter__:
|
||||||
|
raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]: # noqa: F811
|
||||||
|
raise NotImplementedError # more helpful than "None has no attribute..." type errors
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **KW_ONLY, **SLOTS)
|
||||||
|
class Interval(GroupedMetadata):
|
||||||
|
"""Interval can express inclusive or exclusive bounds with a single object.
|
||||||
|
|
||||||
|
It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
|
||||||
|
are interpreted the same way as the single-bound constraints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gt: Union[SupportsGt, None] = None
|
||||||
|
ge: Union[SupportsGe, None] = None
|
||||||
|
lt: Union[SupportsLt, None] = None
|
||||||
|
le: Union[SupportsLe, None] = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||||
|
"""Unpack an Interval into zero or more single-bounds."""
|
||||||
|
if self.gt is not None:
|
||||||
|
yield Gt(self.gt)
|
||||||
|
if self.ge is not None:
|
||||||
|
yield Ge(self.ge)
|
||||||
|
if self.lt is not None:
|
||||||
|
yield Lt(self.lt)
|
||||||
|
if self.le is not None:
|
||||||
|
yield Le(self.le)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MultipleOf(BaseMetadata):
|
||||||
|
"""MultipleOf(multiple_of=x) might be interpreted in two ways:
|
||||||
|
|
||||||
|
1. Python semantics, implying ``value % multiple_of == 0``, or
|
||||||
|
2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
|
||||||
|
|
||||||
|
We encourage users to be aware of these two common interpretations,
|
||||||
|
and libraries to carefully document which they implement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
multiple_of: Union[SupportsDiv, SupportsMod]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MinLen(BaseMetadata):
|
||||||
|
"""
|
||||||
|
MinLen() implies minimum inclusive length,
|
||||||
|
e.g. ``len(value) >= min_length``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
min_length: Annotated[int, Ge(0)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MaxLen(BaseMetadata):
|
||||||
|
"""
|
||||||
|
MaxLen() implies maximum inclusive length,
|
||||||
|
e.g. ``len(value) <= max_length``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
max_length: Annotated[int, Ge(0)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Len(GroupedMetadata):
|
||||||
|
"""
|
||||||
|
Len() implies that ``min_length <= len(value) <= max_length``.
|
||||||
|
|
||||||
|
Upper bound may be omitted or ``None`` to indicate no upper length bound.
|
||||||
|
"""
|
||||||
|
|
||||||
|
min_length: Annotated[int, Ge(0)] = 0
|
||||||
|
max_length: Optional[Annotated[int, Ge(0)]] = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||||
|
"""Unpack a Len into zone or more single-bounds."""
|
||||||
|
if self.min_length > 0:
|
||||||
|
yield MinLen(self.min_length)
|
||||||
|
if self.max_length is not None:
|
||||||
|
yield MaxLen(self.max_length)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Timezone(BaseMetadata):
|
||||||
|
"""Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
|
||||||
|
|
||||||
|
``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
|
||||||
|
``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
|
||||||
|
tz-aware but any timezone is allowed.
|
||||||
|
|
||||||
|
You may also pass a specific timezone string or tzinfo object such as
|
||||||
|
``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
|
||||||
|
you only allow a specific timezone, though we note that this is often
|
||||||
|
a symptom of poor design.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tz: Union[str, tzinfo, EllipsisType, None]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Unit(BaseMetadata):
|
||||||
|
"""Indicates that the value is a physical quantity with the specified unit.
|
||||||
|
|
||||||
|
It is intended for usage with numeric types, where the value represents the
|
||||||
|
magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
|
||||||
|
or ``speed: Annotated[float, Unit('m/s')]``.
|
||||||
|
|
||||||
|
Interpretation of the unit string is left to the discretion of the consumer.
|
||||||
|
It is suggested to follow conventions established by python libraries that work
|
||||||
|
with physical quantities, such as
|
||||||
|
|
||||||
|
- ``pint`` : <https://pint.readthedocs.io/en/stable/>
|
||||||
|
- ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
|
||||||
|
|
||||||
|
For indicating a quantity with a certain dimensionality but without a specific unit
|
||||||
|
it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
|
||||||
|
Note, however, ``annotated_types`` itself makes no use of the unit string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unit: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Predicate(BaseMetadata):
|
||||||
|
"""``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
|
||||||
|
|
||||||
|
Users should prefer statically inspectable metadata, but if you need the full
|
||||||
|
power and flexibility of arbitrary runtime predicates... here it is.
|
||||||
|
|
||||||
|
We provide a few predefined predicates for common string constraints:
|
||||||
|
``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
|
||||||
|
``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
|
||||||
|
can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
|
||||||
|
|
||||||
|
Some libraries might have special logic to handle certain predicates, e.g. by
|
||||||
|
checking for `str.isdigit` and using its presence to both call custom logic to
|
||||||
|
enforce digit-only strings, and customise some generated external schema.
|
||||||
|
|
||||||
|
We do not specify what behaviour should be expected for predicates that raise
|
||||||
|
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||||
|
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||||
|
and then propagate or discard the resulting exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
func: Callable[[Any], bool]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
|
||||||
|
return f"{self.__class__.__name__}({self.func!r})"
|
||||||
|
if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
|
||||||
|
namespace := getattr(self.func.__self__, "__name__", None)
|
||||||
|
):
|
||||||
|
return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
|
||||||
|
if isinstance(self.func, type(str.isascii)): # method descriptor
|
||||||
|
return f"{self.__class__.__name__}({self.func.__qualname__})"
|
||||||
|
return f"{self.__class__.__name__}({self.func.__name__})"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Not:
|
||||||
|
func: Callable[[Any], bool]
|
||||||
|
|
||||||
|
def __call__(self, __v: Any) -> bool:
|
||||||
|
return not self.func(__v)
|
||||||
|
|
||||||
|
|
||||||
|
_StrType = TypeVar("_StrType", bound=str)
|
||||||
|
|
||||||
|
LowerCase = Annotated[_StrType, Predicate(str.islower)]
|
||||||
|
"""
|
||||||
|
Return True if the string is a lowercase string, False otherwise.
|
||||||
|
|
||||||
|
A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
UpperCase = Annotated[_StrType, Predicate(str.isupper)]
|
||||||
|
"""
|
||||||
|
Return True if the string is an uppercase string, False otherwise.
|
||||||
|
|
||||||
|
A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
|
||||||
|
IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
|
||||||
|
"""
|
||||||
|
Return True if the string is a digit string, False otherwise.
|
||||||
|
|
||||||
|
A string is a digit string if all characters in the string are digits and there is at least one character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
IsAscii = Annotated[_StrType, Predicate(str.isascii)]
|
||||||
|
"""
|
||||||
|
Return True if all characters in the string are ASCII, False otherwise.
|
||||||
|
|
||||||
|
ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
|
||||||
|
IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
|
||||||
|
"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
|
||||||
|
IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
|
||||||
|
"""Return True if x is one of infinity or NaN, and False otherwise"""
|
||||||
|
IsNan = Annotated[_NumericType, Predicate(math.isnan)]
|
||||||
|
"""Return True if x is a NaN (not a number), and False otherwise."""
|
||||||
|
IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
|
||||||
|
"""Return True if x is anything but NaN (not a number), and False otherwise."""
|
||||||
|
IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
|
||||||
|
"""Return True if x is a positive or negative infinity, and False otherwise."""
|
||||||
|
IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
|
||||||
|
"""Return True if x is neither a positive or negative infinity, and False otherwise."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class DocInfo: # type: ignore [no-redef]
|
||||||
|
""" "
|
||||||
|
The return value of doc(), mainly to be used by tools that want to extract the
|
||||||
|
Annotated documentation at runtime.
|
||||||
|
"""
|
||||||
|
|
||||||
|
documentation: str
|
||||||
|
"""The documentation string passed to doc()."""
|
||||||
|
|
||||||
|
def doc(
|
||||||
|
documentation: str,
|
||||||
|
) -> DocInfo:
|
||||||
|
"""
|
||||||
|
Add documentation to a type annotation inside of Annotated.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
|
||||||
|
"""
|
||||||
|
return DocInfo(documentation)
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,151 @@
|
|||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
else:
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import annotated_types as at
|
||||||
|
|
||||||
|
|
||||||
|
class Case(NamedTuple):
|
||||||
|
"""
|
||||||
|
A test case for `annotated_types`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
annotation: Any
|
||||||
|
valid_cases: Iterable[Any]
|
||||||
|
invalid_cases: Iterable[Any]
|
||||||
|
|
||||||
|
|
||||||
|
def cases() -> Iterable[Case]:
|
||||||
|
# Gt, Ge, Lt, Le
|
||||||
|
yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
|
||||||
|
yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(date(2000, 1, 1))],
|
||||||
|
[date(2000, 1, 2), date(2000, 1, 3)],
|
||||||
|
[date(2000, 1, 1), date(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(Decimal('1.123'))],
|
||||||
|
[Decimal('1.1231'), Decimal('123')],
|
||||||
|
[Decimal('1.123'), Decimal('0')],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
|
||||||
|
yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(1998, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
|
||||||
|
yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
|
||||||
|
[datetime(1999, 12, 31), datetime(1999, 12, 31)],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
|
||||||
|
yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Le(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Interval
|
||||||
|
yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
|
||||||
|
yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
|
||||||
|
yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 4)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
|
||||||
|
yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
|
||||||
|
|
||||||
|
# lengths
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||||
|
yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||||
|
yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||||
|
yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
|
||||||
|
yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
|
||||||
|
yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||||
|
yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
|
||||||
|
yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
|
||||||
|
|
||||||
|
yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
|
||||||
|
yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
|
||||||
|
yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
|
||||||
|
|
||||||
|
# Timezone
|
||||||
|
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(timezone.utc)],
|
||||||
|
[datetime(2000, 1, 1, tzinfo=timezone.utc)],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone('Europe/London')],
|
||||||
|
[datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Quantity
|
||||||
|
|
||||||
|
yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
|
||||||
|
|
||||||
|
# predicate types
|
||||||
|
|
||||||
|
yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
|
||||||
|
yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
|
||||||
|
yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
|
||||||
|
yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
|
||||||
|
|
||||||
|
yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
|
||||||
|
yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
|
||||||
|
yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
|
||||||
|
yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
|
||||||
|
yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
|
||||||
|
yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
|
||||||
|
|
||||||
|
# check stacked predicates
|
||||||
|
yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
|
||||||
|
|
||||||
|
# doc
|
||||||
|
yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
|
||||||
|
|
||||||
|
# custom GroupedMetadata
|
||||||
|
class MyCustomGroupedMetadata(at.GroupedMetadata):
|
||||||
|
def __iter__(self) -> Iterator[at.Predicate]:
|
||||||
|
yield at.Predicate(lambda x: float(x).is_integer())
|
||||||
|
|
||||||
|
yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: anyio
|
||||||
|
Version: 4.13.0
|
||||||
|
Summary: High-level concurrency and networking framework on top of asyncio or Trio
|
||||||
|
Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
|
||||||
|
License-Expression: MIT
|
||||||
|
Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
|
||||||
|
Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
|
||||||
|
Project-URL: Source code, https://github.com/agronholm/anyio
|
||||||
|
Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Framework :: AnyIO
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
|
Classifier: Programming Language :: Python :: 3.14
|
||||||
|
Requires-Python: >=3.10
|
||||||
|
Description-Content-Type: text/x-rst
|
||||||
|
License-File: LICENSE
|
||||||
|
Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
|
||||||
|
Requires-Dist: idna>=2.8
|
||||||
|
Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
|
||||||
|
Provides-Extra: trio
|
||||||
|
Requires-Dist: trio>=0.32.0; extra == "trio"
|
||||||
|
Dynamic: license-file
|
||||||
|
|
||||||
|
.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
|
||||||
|
:target: https://github.com/agronholm/anyio/actions/workflows/test.yml
|
||||||
|
:alt: Build Status
|
||||||
|
.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
|
||||||
|
:target: https://coveralls.io/github/agronholm/anyio?branch=master
|
||||||
|
:alt: Code Coverage
|
||||||
|
.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
|
||||||
|
:target: https://anyio.readthedocs.io/en/latest/?badge=latest
|
||||||
|
:alt: Documentation
|
||||||
|
.. image:: https://badges.gitter.im/gitterHQ/gitter.svg
|
||||||
|
:target: https://gitter.im/python-trio/AnyIO
|
||||||
|
:alt: Gitter chat
|
||||||
|
.. image:: https://tidelift.com/badges/package/pypi/anyio
|
||||||
|
:target: https://tidelift.com/subscription/pkg/pypi-anyio
|
||||||
|
:alt: Tidelift
|
||||||
|
|
||||||
|
AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
|
||||||
|
Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
|
||||||
|
with the native SC of Trio itself.
|
||||||
|
|
||||||
|
Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
|
||||||
|
Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
|
||||||
|
refactoring necessary. It will blend in with the native libraries of your chosen backend.
|
||||||
|
|
||||||
|
To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
|
||||||
|
`here <https://anyio.readthedocs.io/en/stable/why.html>`_.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
View full documentation at: https://anyio.readthedocs.io/
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
AnyIO offers the following functionality:
|
||||||
|
|
||||||
|
* Task groups (nurseries_ in trio terminology)
|
||||||
|
* High-level networking (TCP, UDP and UNIX sockets)
|
||||||
|
|
||||||
|
* `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
|
||||||
|
3.8)
|
||||||
|
* async/await style UDP sockets (unlike asyncio where you still have to use Transports and
|
||||||
|
Protocols)
|
||||||
|
|
||||||
|
* A versatile API for byte streams and object streams
|
||||||
|
* Inter-task synchronization and communication (locks, conditions, events, semaphores, object
|
||||||
|
streams)
|
||||||
|
* Worker threads
|
||||||
|
* Subprocesses
|
||||||
|
* Subinterpreter support for code parallelization (on Python 3.13 and later)
|
||||||
|
* Asynchronous file I/O (using worker threads)
|
||||||
|
* Signal handling
|
||||||
|
* Asynchronous version of the functools_ module
|
||||||
|
|
||||||
|
AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
|
||||||
|
It even works with the popular Hypothesis_ library.
|
||||||
|
|
||||||
|
.. _asyncio: https://docs.python.org/3/library/asyncio.html
|
||||||
|
.. _Trio: https://github.com/python-trio/trio
|
||||||
|
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
||||||
|
.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
|
||||||
|
.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
|
||||||
|
.. _pytest: https://docs.pytest.org/en/latest/
|
||||||
|
.. _functools: https://docs.python.org/3/library/functools.html
|
||||||
|
.. _Hypothesis: https://hypothesis.works/
|
||||||
|
|
||||||
|
Security contact information
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
To report a security vulnerability, please use the `Tidelift security contact`_.
|
||||||
|
Tidelift will coordinate the fix and disclosure.
|
||||||
|
|
||||||
|
.. _Tidelift security contact: https://tidelift.com/security
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
anyio-4.13.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
anyio-4.13.0.dist-info/METADATA,sha256=F0EYfiPlmTRwmJN2JktNxJg1GNnl0wHhzOWmz7pFvjM,4513
|
||||||
|
anyio-4.13.0.dist-info/RECORD,,
|
||||||
|
anyio-4.13.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
||||||
|
anyio-4.13.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
|
||||||
|
anyio-4.13.0.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
|
||||||
|
anyio-4.13.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
|
||||||
|
anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170
|
||||||
|
anyio/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/from_thread.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/functools.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/lowlevel.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/pytest_plugin.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/to_interpreter.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/to_process.cpython-312.pyc,,
|
||||||
|
anyio/__pycache__/to_thread.cpython-312.pyc,,
|
||||||
|
anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
anyio/_backends/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
anyio/_backends/__pycache__/_asyncio.cpython-312.pyc,,
|
||||||
|
anyio/_backends/__pycache__/_trio.cpython-312.pyc,,
|
||||||
|
anyio/_backends/_asyncio.py,sha256=kuqlg2sBUsFdgY80xSDAw60Gx_4WNCl9iSL5XlY6lCU,99476
|
||||||
|
anyio/_backends/_trio.py,sha256=l9U-TsKRxzmTQxSMvOhn0bNeFn_iRx3Ho30jvR5Bdu0,41366
|
||||||
|
anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
anyio/_core/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_eventloop.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_exceptions.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_fileio.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_resources.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_signals.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_sockets.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_streams.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_subprocesses.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_synchronization.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_tasks.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_tempfile.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_testing.cpython-312.pyc,,
|
||||||
|
anyio/_core/__pycache__/_typedattr.cpython-312.pyc,,
|
||||||
|
anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
|
||||||
|
anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
|
||||||
|
anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448
|
||||||
|
anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407
|
||||||
|
anyio/_core/_fileio.py,sha256=CKi1gFNiW2G4knWeBE7He7-rptQwgYjDUWfG8DSlvLs,25665
|
||||||
|
anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
|
||||||
|
anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
|
||||||
|
anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977
|
||||||
|
anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
|
||||||
|
anyio/_core/_subprocesses.py,sha256=tkmkPKEkEaiMD8C9WRZBlmgjOYRDRbZdte6e-unay2E,7916
|
||||||
|
anyio/_core/_synchronization.py,sha256=9G3fvRsPNrrWJ_Z6gD_80wXq8I8qgAyhwM8PvHQnT2c,21061
|
||||||
|
anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435
|
||||||
|
anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624
|
||||||
|
anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
|
||||||
|
anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
|
||||||
|
anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
|
||||||
|
anyio/abc/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_eventloop.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_resources.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_sockets.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_streams.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_subprocesses.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_tasks.cpython-312.pyc,,
|
||||||
|
anyio/abc/__pycache__/_testing.cpython-312.pyc,,
|
||||||
|
anyio/abc/_eventloop.py,sha256=39lYnmtvoHaZw22sWBKOTA_zv7bamOnr8O49PqgDXdw,10629
|
||||||
|
anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
|
||||||
|
anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124
|
||||||
|
anyio/abc/_streams.py,sha256=HYvna1iZbWcwLROTO6IhLX79RTRLPShZMWe0sG1q54I,7481
|
||||||
|
anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
|
||||||
|
anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721
|
||||||
|
anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821
|
||||||
|
anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141
|
||||||
|
anyio/functools.py,sha256=5AWM1iYTKkTzptvUhQDdLSh5GvbBW-vcs-SAUfIfA9A,12076
|
||||||
|
anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158
|
||||||
|
anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
anyio/pytest_plugin.py,sha256=t6h4KJstqIxfxwTZ1YO8vpUVuB99nfCLltn0NHfatHo,12775
|
||||||
|
anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
anyio/streams/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/buffered.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/file.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/memory.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/stapled.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/text.cpython-312.pyc,,
|
||||||
|
anyio/streams/__pycache__/tls.cpython-312.pyc,,
|
||||||
|
anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263
|
||||||
|
anyio/streams/file.py,sha256=msnrotVKGMQomUu_Rj2qz9MvIdUp6d3JGr7MOEO8kV4,4428
|
||||||
|
anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740
|
||||||
|
anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390
|
||||||
|
anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
|
||||||
|
anyio/streams/tls.py,sha256=DQVkXUvsTEYKkBO8dlVU7j_5H8QOtLy4sGi1Wrjqevo,15303
|
||||||
|
anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
|
||||||
|
anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798
|
||||||
|
anyio/to_thread.py,sha256=f6h_k2d743GBv9FhAnhM_YpTvWgIrzBy9cOE0eJ1UJw,2693
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: setuptools (82.0.1)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[pytest11]
|
||||||
|
anyio = anyio.pytest_plugin
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Alex Grönholm
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
anyio
|
||||||
111
backend/venv/lib/python3.12/site-packages/anyio/__init__.py
Normal file
111
backend/venv/lib/python3.12/site-packages/anyio/__init__.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
|
||||||
|
from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
|
||||||
|
from ._core._eventloop import current_time as current_time
|
||||||
|
from ._core._eventloop import get_all_backends as get_all_backends
|
||||||
|
from ._core._eventloop import get_available_backends as get_available_backends
|
||||||
|
from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
|
||||||
|
from ._core._eventloop import run as run
|
||||||
|
from ._core._eventloop import sleep as sleep
|
||||||
|
from ._core._eventloop import sleep_forever as sleep_forever
|
||||||
|
from ._core._eventloop import sleep_until as sleep_until
|
||||||
|
from ._core._exceptions import BrokenResourceError as BrokenResourceError
|
||||||
|
from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
|
||||||
|
from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
|
||||||
|
from ._core._exceptions import BusyResourceError as BusyResourceError
|
||||||
|
from ._core._exceptions import ClosedResourceError as ClosedResourceError
|
||||||
|
from ._core._exceptions import ConnectionFailed as ConnectionFailed
|
||||||
|
from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
|
||||||
|
from ._core._exceptions import EndOfStream as EndOfStream
|
||||||
|
from ._core._exceptions import IncompleteRead as IncompleteRead
|
||||||
|
from ._core._exceptions import NoEventLoopError as NoEventLoopError
|
||||||
|
from ._core._exceptions import RunFinishedError as RunFinishedError
|
||||||
|
from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
|
||||||
|
from ._core._exceptions import WouldBlock as WouldBlock
|
||||||
|
from ._core._fileio import AsyncFile as AsyncFile
|
||||||
|
from ._core._fileio import Path as Path
|
||||||
|
from ._core._fileio import open_file as open_file
|
||||||
|
from ._core._fileio import wrap_file as wrap_file
|
||||||
|
from ._core._resources import aclose_forcefully as aclose_forcefully
|
||||||
|
from ._core._signals import open_signal_receiver as open_signal_receiver
|
||||||
|
from ._core._sockets import TCPConnectable as TCPConnectable
|
||||||
|
from ._core._sockets import UNIXConnectable as UNIXConnectable
|
||||||
|
from ._core._sockets import as_connectable as as_connectable
|
||||||
|
from ._core._sockets import connect_tcp as connect_tcp
|
||||||
|
from ._core._sockets import connect_unix as connect_unix
|
||||||
|
from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
|
||||||
|
from ._core._sockets import (
|
||||||
|
create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
|
||||||
|
)
|
||||||
|
from ._core._sockets import create_tcp_listener as create_tcp_listener
|
||||||
|
from ._core._sockets import create_udp_socket as create_udp_socket
|
||||||
|
from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
|
||||||
|
from ._core._sockets import create_unix_listener as create_unix_listener
|
||||||
|
from ._core._sockets import getaddrinfo as getaddrinfo
|
||||||
|
from ._core._sockets import getnameinfo as getnameinfo
|
||||||
|
from ._core._sockets import notify_closing as notify_closing
|
||||||
|
from ._core._sockets import wait_readable as wait_readable
|
||||||
|
from ._core._sockets import wait_socket_readable as wait_socket_readable
|
||||||
|
from ._core._sockets import wait_socket_writable as wait_socket_writable
|
||||||
|
from ._core._sockets import wait_writable as wait_writable
|
||||||
|
from ._core._streams import create_memory_object_stream as create_memory_object_stream
|
||||||
|
from ._core._subprocesses import open_process as open_process
|
||||||
|
from ._core._subprocesses import run_process as run_process
|
||||||
|
from ._core._synchronization import CapacityLimiter as CapacityLimiter
|
||||||
|
from ._core._synchronization import (
|
||||||
|
CapacityLimiterStatistics as CapacityLimiterStatistics,
|
||||||
|
)
|
||||||
|
from ._core._synchronization import Condition as Condition
|
||||||
|
from ._core._synchronization import ConditionStatistics as ConditionStatistics
|
||||||
|
from ._core._synchronization import Event as Event
|
||||||
|
from ._core._synchronization import EventStatistics as EventStatistics
|
||||||
|
from ._core._synchronization import Lock as Lock
|
||||||
|
from ._core._synchronization import LockStatistics as LockStatistics
|
||||||
|
from ._core._synchronization import ResourceGuard as ResourceGuard
|
||||||
|
from ._core._synchronization import Semaphore as Semaphore
|
||||||
|
from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
|
||||||
|
from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
|
||||||
|
from ._core._tasks import CancelScope as CancelScope
|
||||||
|
from ._core._tasks import create_task_group as create_task_group
|
||||||
|
from ._core._tasks import current_effective_deadline as current_effective_deadline
|
||||||
|
from ._core._tasks import fail_after as fail_after
|
||||||
|
from ._core._tasks import move_on_after as move_on_after
|
||||||
|
from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
|
||||||
|
from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
|
||||||
|
from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
|
||||||
|
from ._core._tempfile import TemporaryFile as TemporaryFile
|
||||||
|
from ._core._tempfile import gettempdir as gettempdir
|
||||||
|
from ._core._tempfile import gettempdirb as gettempdirb
|
||||||
|
from ._core._tempfile import mkdtemp as mkdtemp
|
||||||
|
from ._core._tempfile import mkstemp as mkstemp
|
||||||
|
from ._core._testing import TaskInfo as TaskInfo
|
||||||
|
from ._core._testing import get_current_task as get_current_task
|
||||||
|
from ._core._testing import get_running_tasks as get_running_tasks
|
||||||
|
from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
|
||||||
|
from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
|
||||||
|
from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
|
||||||
|
from ._core._typedattr import typed_attribute as typed_attribute
|
||||||
|
|
||||||
|
# Re-export imports so they look like they live directly in this package
|
||||||
|
for __value in list(locals().values()):
|
||||||
|
if getattr(__value, "__module__", "").startswith("anyio."):
|
||||||
|
__value.__module__ = __name__
|
||||||
|
|
||||||
|
|
||||||
|
del __value
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
|
||||||
|
"""Support deprecated aliases."""
|
||||||
|
if attr == "BrokenWorkerIntepreter":
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return BrokenWorkerInterpreter
|
||||||
|
|
||||||
|
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
1343
backend/venv/lib/python3.12/site-packages/anyio/_backends/_trio.py
Normal file
1343
backend/venv/lib/python3.12/site-packages/anyio/_backends/_trio.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,167 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
from collections.abc import Callable
|
||||||
|
from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from _typeshed import FileDescriptorLike
|
||||||
|
|
||||||
|
_selector_lock = threading.Lock()
|
||||||
|
_selector: Selector | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Selector:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._thread = threading.Thread(target=self.run, name="AnyIO socket selector")
|
||||||
|
self._selector = DefaultSelector()
|
||||||
|
self._send, self._receive = socket.socketpair()
|
||||||
|
self._send.setblocking(False)
|
||||||
|
self._receive.setblocking(False)
|
||||||
|
# This somewhat reduces the amount of memory wasted queueing up data
|
||||||
|
# for wakeups. With these settings, maximum number of 1-byte sends
|
||||||
|
# before getting BlockingIOError:
|
||||||
|
# Linux 4.8: 6
|
||||||
|
# macOS (darwin 15.5): 1
|
||||||
|
# Windows 10: 525347
|
||||||
|
# Windows you're weird. (And on Windows setting SNDBUF to 0 makes send
|
||||||
|
# blocking, even on non-blocking sockets, so don't do that.)
|
||||||
|
self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1)
|
||||||
|
self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1)
|
||||||
|
# On Windows this is a TCP socket so this might matter. On other
|
||||||
|
# platforms this fails b/c AF_UNIX sockets aren't actually TCP.
|
||||||
|
try:
|
||||||
|
self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._selector.register(self._receive, EVENT_READ)
|
||||||
|
self._closed = False
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
self._thread.start()
|
||||||
|
threading._register_atexit(self._stop) # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
def _stop(self) -> None:
|
||||||
|
global _selector
|
||||||
|
self._closed = True
|
||||||
|
self._notify_self()
|
||||||
|
self._send.close()
|
||||||
|
self._thread.join()
|
||||||
|
self._selector.unregister(self._receive)
|
||||||
|
self._receive.close()
|
||||||
|
self._selector.close()
|
||||||
|
_selector = None
|
||||||
|
assert not self._selector.get_map(), (
|
||||||
|
"selector still has registered file descriptors after shutdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _notify_self(self) -> None:
|
||||||
|
try:
|
||||||
|
self._send.send(b"\x00")
|
||||||
|
except BlockingIOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
try:
|
||||||
|
key = self._selector.get_key(fd)
|
||||||
|
except KeyError:
|
||||||
|
self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)})
|
||||||
|
else:
|
||||||
|
if EVENT_READ in key.data:
|
||||||
|
raise ValueError(
|
||||||
|
"this file descriptor is already registered for reading"
|
||||||
|
)
|
||||||
|
|
||||||
|
key.data[EVENT_READ] = loop, callback
|
||||||
|
self._selector.modify(fd, key.events | EVENT_READ, key.data)
|
||||||
|
|
||||||
|
self._notify_self()
|
||||||
|
|
||||||
|
def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
try:
|
||||||
|
key = self._selector.get_key(fd)
|
||||||
|
except KeyError:
|
||||||
|
self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)})
|
||||||
|
else:
|
||||||
|
if EVENT_WRITE in key.data:
|
||||||
|
raise ValueError(
|
||||||
|
"this file descriptor is already registered for writing"
|
||||||
|
)
|
||||||
|
|
||||||
|
key.data[EVENT_WRITE] = loop, callback
|
||||||
|
self._selector.modify(fd, key.events | EVENT_WRITE, key.data)
|
||||||
|
|
||||||
|
self._notify_self()
|
||||||
|
|
||||||
|
def remove_reader(self, fd: FileDescriptorLike) -> bool:
|
||||||
|
try:
|
||||||
|
key = self._selector.get_key(fd)
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if new_events := key.events ^ EVENT_READ:
|
||||||
|
del key.data[EVENT_READ]
|
||||||
|
self._selector.modify(fd, new_events, key.data)
|
||||||
|
else:
|
||||||
|
self._selector.unregister(fd)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove_writer(self, fd: FileDescriptorLike) -> bool:
|
||||||
|
try:
|
||||||
|
key = self._selector.get_key(fd)
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if new_events := key.events ^ EVENT_WRITE:
|
||||||
|
del key.data[EVENT_WRITE]
|
||||||
|
self._selector.modify(fd, new_events, key.data)
|
||||||
|
else:
|
||||||
|
self._selector.unregister(fd)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
while not self._closed:
|
||||||
|
for key, events in self._selector.select():
|
||||||
|
if key.fileobj is self._receive:
|
||||||
|
try:
|
||||||
|
while self._receive.recv(4096):
|
||||||
|
pass
|
||||||
|
except BlockingIOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
if events & EVENT_READ:
|
||||||
|
loop, callback = key.data[EVENT_READ]
|
||||||
|
self.remove_reader(key.fd)
|
||||||
|
try:
|
||||||
|
loop.call_soon_threadsafe(callback)
|
||||||
|
except RuntimeError:
|
||||||
|
pass # the loop was already closed
|
||||||
|
|
||||||
|
if events & EVENT_WRITE:
|
||||||
|
loop, callback = key.data[EVENT_WRITE]
|
||||||
|
self.remove_writer(key.fd)
|
||||||
|
try:
|
||||||
|
loop.call_soon_threadsafe(callback)
|
||||||
|
except RuntimeError:
|
||||||
|
pass # the loop was already closed
|
||||||
|
|
||||||
|
|
||||||
|
def get_selector() -> Selector:
|
||||||
|
global _selector
|
||||||
|
|
||||||
|
with _selector_lock:
|
||||||
|
if _selector is None:
|
||||||
|
_selector = Selector()
|
||||||
|
_selector.start()
|
||||||
|
|
||||||
|
return _selector
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user