parent
19810e4519
commit
985d70fa2d
@ -1,32 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装 Python 依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制应用代码
|
||||
COPY database.py .
|
||||
COPY data_sync.py .
|
||||
COPY main_api.py .
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/')" || exit 1
|
||||
|
||||
# 启动服务
|
||||
CMD ["python", "-m", "uvicorn", "main_api:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,353 +0,0 @@
|
||||
"""
|
||||
数据库连接和模型模块
|
||||
用于连接 MySQL 数据库并操作数据
|
||||
"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Any
|
||||
from contextlib import contextmanager
|
||||
|
||||
from sqlalchemy import (
|
||||
create_engine, Column, Integer, BigInteger, Float, String,
|
||||
DateTime, Boolean, Text, ForeignKey, Index, UniqueConstraint
|
||||
)
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, Session
|
||||
from sqlalchemy.pool import QueuePool
|
||||
|
||||
# 加载环境变量
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST = os.getenv('DB_HOST', 'mysql')
|
||||
DB_PORT = os.getenv('DB_PORT', '3306')
|
||||
DB_NAME = os.getenv('DB_NAME', 'aguzhitou')
|
||||
DB_USER = os.getenv('DB_USER', 'root')
|
||||
DB_PASSWORD = os.getenv('DB_PASSWORD', '1qazse42W3')
|
||||
|
||||
DATABASE_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
|
||||
# 创建引擎
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
poolclass=QueuePool,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600,
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# ==================== 数据模型 ====================
|
||||
|
||||
class MarketIndex(Base):
|
||||
"""市场指数"""
|
||||
__tablename__ = "market_indices"
|
||||
__table_args__ = (
|
||||
Index('idx_market_indices_code', 'code'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), unique=True, nullable=False)
|
||||
code = Column(String(50), unique=True, nullable=False)
|
||||
current = Column(Float, default=0)
|
||||
change = Column(Float, default=0)
|
||||
changePercent = Column('change_percent', Float, default=0)
|
||||
volume = Column(BigInteger, default=0)
|
||||
turnover = Column(BigInteger, default=0)
|
||||
updatedAt = Column('updated_at', DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
createdAt = Column('created_at', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class Sector(Base):
|
||||
"""板块信息"""
|
||||
__tablename__ = "sectors"
|
||||
__table_args__ = (
|
||||
Index('idx_sectors_code', 'code'),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True)
|
||||
name = Column(String(100), unique=True, nullable=False)
|
||||
code = Column(String(50), unique=True, nullable=False)
|
||||
updatedAt = Column('updated_at', DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
createdAt = Column('created_at', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class SectorQuote(Base):
|
||||
"""板块行情"""
|
||||
__tablename__ = "sector_quotes"
|
||||
__table_args__ = (
|
||||
Index('idx_sector_quotes_code', 'sector_code'),
|
||||
Index('idx_sector_quotes_time', 'quote_time'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
sectorCode = Column('sector_code', String(50), nullable=False)
|
||||
current = Column(Float, default=0)
|
||||
change = Column(Float, default=0)
|
||||
changePercent = Column('change_percent', Float, default=0)
|
||||
volume = Column(BigInteger, default=0)
|
||||
turnover = Column(BigInteger, default=0)
|
||||
momentumScore = Column('momentum_score', Float, default=50)
|
||||
rank = Column(Integer, default=0)
|
||||
previousRank = Column('previous_rank', Integer, default=0)
|
||||
quoteTime = Column('quote_time', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class Stock(Base):
|
||||
"""股票信息"""
|
||||
__tablename__ = "stocks"
|
||||
__table_args__ = (
|
||||
Index('idx_stocks_code', 'code'),
|
||||
Index('idx_stocks_sector', 'sector_code'),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True)
|
||||
code = Column(String(50), unique=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
sectorCode = Column('sector_code', String(50), nullable=True)
|
||||
marketCap = Column('market_cap', BigInteger, nullable=True)
|
||||
pe = Column(Float, nullable=True)
|
||||
pb = Column(Float, nullable=True)
|
||||
updatedAt = Column('updated_at', DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
createdAt = Column('created_at', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class StockQuote(Base):
|
||||
"""股票行情"""
|
||||
__tablename__ = "stock_quotes"
|
||||
__table_args__ = (
|
||||
Index('idx_stock_quotes_code', 'stock_code'),
|
||||
Index('idx_stock_quotes_time', 'quote_time'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
stockCode = Column('stock_code', String(50), nullable=False)
|
||||
price = Column(Float, default=0)
|
||||
open = Column(Float, default=0)
|
||||
high = Column(Float, default=0)
|
||||
low = Column(Float, default=0)
|
||||
preClose = Column('pre_close', Float, default=0)
|
||||
volume = Column(BigInteger, default=0)
|
||||
turnover = Column(BigInteger, default=0)
|
||||
changePercent = Column('change_percent', Float, default=0)
|
||||
turnoverRate = Column('turnover_rate', Float, nullable=True)
|
||||
amplitude = Column(Float, nullable=True)
|
||||
quoteTime = Column('quote_time', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class StockKLine(Base):
|
||||
"""股票K线数据"""
|
||||
__tablename__ = "stock_klines"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('stock_code', 'period', 'date', name='uk_stock_klines'),
|
||||
Index('idx_stock_klines_code', 'stock_code'),
|
||||
Index('idx_stock_klines_date', 'date'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
stockCode = Column('stock_code', String(50), nullable=False)
|
||||
period = Column(String(20), nullable=False) # day/week/month
|
||||
date = Column(DateTime, nullable=False)
|
||||
open = Column(Float, default=0)
|
||||
high = Column(Float, default=0)
|
||||
low = Column(Float, default=0)
|
||||
close = Column(Float, default=0)
|
||||
volume = Column(BigInteger, default=0)
|
||||
ma5 = Column(Float, nullable=True)
|
||||
ma10 = Column(Float, nullable=True)
|
||||
ma20 = Column(Float, nullable=True)
|
||||
ma30 = Column(Float, nullable=True)
|
||||
ma60 = Column(Float, nullable=True)
|
||||
|
||||
|
||||
class SectorKLine(Base):
|
||||
"""板块K线数据"""
|
||||
__tablename__ = "sector_klines"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('sector_code', 'period', 'date', name='uk_sector_klines'),
|
||||
Index('idx_sector_klines_code', 'sector_code'),
|
||||
Index('idx_sector_klines_date', 'date'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
sectorCode = Column('sector_code', String(50), nullable=False)
|
||||
period = Column(String(20), nullable=False)
|
||||
date = Column(DateTime, nullable=False)
|
||||
open = Column(Float, default=0)
|
||||
high = Column(Float, default=0)
|
||||
low = Column(Float, default=0)
|
||||
close = Column(Float, default=0)
|
||||
volume = Column(BigInteger, default=0)
|
||||
|
||||
|
||||
class HighLowStock(Base):
|
||||
"""新高新低股票记录"""
|
||||
__tablename__ = "high_low_stocks"
|
||||
__table_args__ = (
|
||||
Index('idx_high_low_code', 'stock_code'),
|
||||
Index('idx_high_low_type', 'type'),
|
||||
Index('idx_high_low_date', 'date'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
stockCode = Column('stock_code', String(50), nullable=False)
|
||||
type = Column(String(10), nullable=False) # high/low
|
||||
price = Column(Float, default=0)
|
||||
date = Column(DateTime, nullable=False)
|
||||
daysToHighLow = Column('days_to_highlow', Integer, default=0)
|
||||
createdAt = Column('created_at', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
class MomentumStock(Base):
|
||||
"""动量股票推荐"""
|
||||
__tablename__ = "momentum_stocks"
|
||||
__table_args__ = (
|
||||
Index('idx_momentum_code', 'stock_code'),
|
||||
Index('idx_momentum_date', 'date'),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
stockCode = Column('stock_code', String(50), nullable=False)
|
||||
momentumScore = Column('momentum_score', Float, default=0)
|
||||
tags = Column(Text, nullable=True) # JSON string
|
||||
volumeRatio = Column('volume_ratio', Float, default=0)
|
||||
breakThrough = Column('break_through', Boolean, default=False)
|
||||
date = Column(DateTime, nullable=False)
|
||||
createdAt = Column('created_at', DateTime, default=datetime.now)
|
||||
|
||||
|
||||
# ==================== 数据库操作 ====================
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
"""获取数据库会话上下文管理器"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库表"""
|
||||
Base.metadata.create_all(bind=engine)
|
||||
print("数据库表初始化完成")
|
||||
|
||||
|
||||
def check_connection() -> bool:
|
||||
"""检查数据库连接"""
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
conn.execute("SELECT 1")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"数据库连接失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# ==================== 数据操作函数 ====================
|
||||
|
||||
def upsert_stock(db: Session, code: str, name: str, **kwargs):
|
||||
"""插入或更新股票信息"""
|
||||
stock = db.query(Stock).filter(Stock.code == code).first()
|
||||
if stock:
|
||||
stock.name = name
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(stock, key):
|
||||
setattr(stock, key, value)
|
||||
stock.updatedAt = datetime.now()
|
||||
else:
|
||||
import uuid
|
||||
stock = Stock(
|
||||
id=str(uuid.uuid4()),
|
||||
code=code,
|
||||
name=name,
|
||||
**kwargs
|
||||
)
|
||||
db.add(stock)
|
||||
return stock
|
||||
|
||||
|
||||
def upsert_stock_quote(db: Session, stock_code: str, **data):
|
||||
"""插入股票行情"""
|
||||
quote = StockQuote(
|
||||
stockCode=stock_code,
|
||||
**data
|
||||
)
|
||||
db.add(quote)
|
||||
return quote
|
||||
|
||||
|
||||
def upsert_stock_kline(db: Session, stock_code: str, period: str, date: datetime, **data):
|
||||
"""插入或更新K线数据"""
|
||||
kline = db.query(StockKLine).filter(
|
||||
StockKLine.stockCode == stock_code,
|
||||
StockKLine.period == period,
|
||||
StockKLine.date == date
|
||||
).first()
|
||||
|
||||
if kline:
|
||||
for key, value in data.items():
|
||||
if hasattr(kline, key):
|
||||
setattr(kline, key, value)
|
||||
else:
|
||||
kline = StockKLine(
|
||||
stockCode=stock_code,
|
||||
period=period,
|
||||
date=date,
|
||||
**data
|
||||
)
|
||||
db.add(kline)
|
||||
return kline
|
||||
|
||||
|
||||
def upsert_sector(db: Session, code: str, name: str):
|
||||
"""插入或更新板块信息"""
|
||||
sector = db.query(Sector).filter(Sector.code == code).first()
|
||||
if sector:
|
||||
sector.name = name
|
||||
sector.updatedAt = datetime.now()
|
||||
else:
|
||||
import uuid
|
||||
sector = Sector(
|
||||
id=str(uuid.uuid4()),
|
||||
code=code,
|
||||
name=name
|
||||
)
|
||||
db.add(sector)
|
||||
return sector
|
||||
|
||||
|
||||
def upsert_market_index(db: Session, code: str, name: str, **data):
|
||||
"""插入或更新市场指数"""
|
||||
index = db.query(MarketIndex).filter(MarketIndex.code == code).first()
|
||||
if index:
|
||||
index.name = name
|
||||
for key, value in data.items():
|
||||
if hasattr(index, key):
|
||||
setattr(index, key, value)
|
||||
else:
|
||||
index = MarketIndex(
|
||||
code=code,
|
||||
name=name,
|
||||
**data
|
||||
)
|
||||
db.add(index)
|
||||
return index
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试数据库连接
|
||||
if check_connection():
|
||||
init_db()
|
||||
print("数据库初始化成功")
|
||||
else:
|
||||
print("数据库连接失败,请检查配置")
|
||||
@ -1,214 +0,0 @@
|
||||
"""
|
||||
AKShare HTTP API 服务
|
||||
提供股票数据接口
|
||||
"""
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import akshare as ak
|
||||
import pandas as pd
|
||||
from typing import Optional, List
|
||||
import json
|
||||
|
||||
app = FastAPI(
|
||||
title="AKShare HTTP API",
|
||||
description="AKShare 数据接口 HTTP 服务",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# 配置 CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
def dataframe_to_records(df: pd.DataFrame) -> List[dict]:
|
||||
"""将 DataFrame 转换为可 JSON 序列化的记录列表"""
|
||||
if df is None or df.empty:
|
||||
return []
|
||||
# 处理 NaN 值
|
||||
df = df.replace({pd.NaT: None})
|
||||
df = df.where(pd.notnull(df), None)
|
||||
return df.to_dict('records')
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""健康检查"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "AKShare HTTP API",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/stock_zh_a_spot")
|
||||
async def stock_zh_a_spot():
|
||||
"""
|
||||
获取 A 股实时行情数据
|
||||
"""
|
||||
try:
|
||||
df = ak.stock_zh_a_spot_em()
|
||||
records = dataframe_to_records(df)
|
||||
# 字段映射,统一返回格式
|
||||
mapped_records = []
|
||||
for record in records:
|
||||
mapped_records.append({
|
||||
"code": record.get("代码"),
|
||||
"name": record.get("名称"),
|
||||
"price": record.get("最新价", 0),
|
||||
"change": record.get("涨跌额", 0),
|
||||
"change_percent": record.get("涨跌幅", 0),
|
||||
"volume": record.get("成交量", 0),
|
||||
"turnover": record.get("成交额", 0),
|
||||
"open": record.get("开盘价", 0),
|
||||
"high": record.get("最高价", 0),
|
||||
"low": record.get("最低价", 0),
|
||||
"pre_close": record.get("昨收", 0),
|
||||
"turnover_rate": record.get("换手率", 0),
|
||||
"amplitude": record.get("振幅", 0),
|
||||
"market_cap": record.get("总市值", 0),
|
||||
"pe": record.get("市盈率-动态", 0),
|
||||
"pb": record.get("市净率", 0),
|
||||
"industry": record.get("行业", ""),
|
||||
})
|
||||
return mapped_records
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取数据失败: {str(e)}")
|
||||
|
||||
|
||||
@app.get("/stock_zh_a_hist")
|
||||
async def stock_zh_a_hist(
|
||||
symbol: str = Query(..., description="股票代码,如 000001"),
|
||||
period: str = Query("daily", description="周期: daily/weekly/monthly"),
|
||||
start_date: Optional[str] = Query(None, description="开始日期 YYYYMMDD"),
|
||||
end_date: Optional[str] = Query(None, description="结束日期 YYYYMMDD"),
|
||||
adjust: str = Query("qfq", description="复权方式: qfq-前复权, hfq-后复权, 不复权")
|
||||
):
|
||||
"""
|
||||
获取 A 股历史 K 线数据
|
||||
"""
|
||||
try:
|
||||
# 转换周期参数
|
||||
period_map = {
|
||||
"daily": "daily",
|
||||
"weekly": "weekly",
|
||||
"monthly": "monthly"
|
||||
}
|
||||
ak_period = period_map.get(period, "daily")
|
||||
|
||||
# 转换复权参数
|
||||
adjust_map = {
|
||||
"qfq": "qfq",
|
||||
"hfq": "hfq",
|
||||
"": ""
|
||||
}
|
||||
ak_adjust = adjust_map.get(adjust, "qfq")
|
||||
|
||||
df = ak.stock_zh_a_hist(
|
||||
symbol=symbol,
|
||||
period=ak_period,
|
||||
start_date=start_date or "19700101",
|
||||
end_date=end_date or "20500101",
|
||||
adjust=ak_adjust
|
||||
)
|
||||
records = dataframe_to_records(df)
|
||||
|
||||
# 字段映射
|
||||
mapped_records = []
|
||||
for record in records:
|
||||
mapped_records.append({
|
||||
"date": record.get("日期"),
|
||||
"open": record.get("开盘", 0),
|
||||
"high": record.get("最高", 0),
|
||||
"low": record.get("最低", 0),
|
||||
"close": record.get("收盘", 0),
|
||||
"volume": record.get("成交量", 0),
|
||||
"turnover": record.get("成交额", 0),
|
||||
"amplitude": record.get("振幅", 0),
|
||||
"change": record.get("涨跌幅", 0),
|
||||
"change_amount": record.get("涨跌额", 0),
|
||||
"turnover_rate": record.get("换手率", 0),
|
||||
})
|
||||
return mapped_records
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取数据失败: {str(e)}")
|
||||
|
||||
|
||||
@app.get("/stock_zh_index_spot")
|
||||
async def stock_zh_index_spot():
|
||||
"""
|
||||
获取股票指数实时行情
|
||||
"""
|
||||
try:
|
||||
df = ak.index_zh_a_spot_em()
|
||||
records = dataframe_to_records(df)
|
||||
mapped_records = []
|
||||
for record in records:
|
||||
mapped_records.append({
|
||||
"code": record.get("代码"),
|
||||
"name": record.get("名称"),
|
||||
"price": record.get("最新价", 0),
|
||||
"change": record.get("涨跌额", 0),
|
||||
"change_percent": record.get("涨跌幅", 0),
|
||||
"volume": record.get("成交量", 0),
|
||||
"turnover": record.get("成交额", 0),
|
||||
"open": record.get("开盘价", 0),
|
||||
"high": record.get("最高价", 0),
|
||||
"low": record.get("最低价", 0),
|
||||
"pre_close": record.get("昨收", 0),
|
||||
})
|
||||
return mapped_records
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取数据失败: {str(e)}")
|
||||
|
||||
|
||||
@app.get("/stock_sector_spot")
|
||||
async def stock_sector_spot():
|
||||
"""
|
||||
获取板块行情数据
|
||||
"""
|
||||
try:
|
||||
df = ak.stock_board_industry_name_em()
|
||||
records = dataframe_to_records(df)
|
||||
mapped_records = []
|
||||
for record in records:
|
||||
mapped_records.append({
|
||||
"code": record.get("代码"),
|
||||
"name": record.get("名称"),
|
||||
"change_percent": record.get("涨跌幅", 0),
|
||||
})
|
||||
return mapped_records
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取数据失败: {str(e)}")
|
||||
|
||||
|
||||
@app.get("/stock_sector_cons")
|
||||
async def stock_sector_cons(
|
||||
symbol: str = Query(..., description="板块名称")
|
||||
):
|
||||
"""
|
||||
获取板块成分股
|
||||
"""
|
||||
try:
|
||||
df = ak.stock_board_industry_cons_em(symbol=symbol)
|
||||
records = dataframe_to_records(df)
|
||||
mapped_records = []
|
||||
for record in records:
|
||||
mapped_records.append({
|
||||
"code": record.get("代码"),
|
||||
"name": record.get("名称"),
|
||||
"price": record.get("最新价", 0),
|
||||
"change_percent": record.get("涨跌幅", 0),
|
||||
})
|
||||
return mapped_records
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取数据失败: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
@ -1,10 +0,0 @@
|
||||
akshare>=1.14.0
|
||||
fastapi>=0.100.0
|
||||
uvicorn[standard]>=0.23.0
|
||||
pandas>=2.0.0
|
||||
numpy>=1.24.0
|
||||
pymysql>=1.1.0
|
||||
sqlalchemy>=2.0.0
|
||||
cryptography>=41.0.0
|
||||
python-dotenv>=1.0.0
|
||||
schedule>=1.2.0
|
||||
@ -1,20 +0,0 @@
|
||||
"""
|
||||
AKShare HTTP 服务启动脚本
|
||||
Windows 用户双击运行或 python start.py 启动
|
||||
"""
|
||||
import uvicorn
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting AKShare HTTP API Server...")
|
||||
print("URL: http://localhost:8000")
|
||||
print("Press Ctrl+C to stop")
|
||||
print("-" * 50)
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=False,
|
||||
log_level="info"
|
||||
)
|
||||
@ -1,2 +0,0 @@
|
||||
"""Market Data Service - Python实现"""
|
||||
__version__ = "1.0.0"
|
||||
Binary file not shown.
Binary file not shown.
@ -1,13 +0,0 @@
|
||||
"""数据源适配器模块"""
|
||||
from .base import DataSourceAdapter, TickData, KLineData, SymbolInfo, TradeCalData, TickCallback
|
||||
from .tushare_adapter import TushareAdapter
|
||||
|
||||
__all__ = [
|
||||
"DataSourceAdapter",
|
||||
"TickData",
|
||||
"KLineData",
|
||||
"SymbolInfo",
|
||||
"TradeCalData",
|
||||
"TickCallback",
|
||||
"TushareAdapter",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,102 +0,0 @@
|
||||
"""数据源适配器基类 - 对应Go的adapter/adapter.go"""
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class TickData:
|
||||
"""Tick数据"""
|
||||
symbol: str
|
||||
price: float
|
||||
volume: int
|
||||
time: int # Unix时间戳
|
||||
|
||||
|
||||
@dataclass
|
||||
class KLineData:
|
||||
"""K线数据"""
|
||||
symbol: str
|
||||
time: int # Unix时间戳
|
||||
open: float
|
||||
high: float
|
||||
low: float
|
||||
close: float
|
||||
volume: int
|
||||
amount: float
|
||||
open_interest: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class SymbolInfo:
|
||||
"""标的信息"""
|
||||
symbol_id: str
|
||||
name: str
|
||||
exchange: str
|
||||
underlying: str = "" # 期货品种代码
|
||||
contract_month: str = ""
|
||||
list_date: str = ""
|
||||
delist_date: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradeCalData:
|
||||
"""交易日历数据"""
|
||||
date: datetime
|
||||
is_trading_day: bool
|
||||
has_night_session: bool = False
|
||||
|
||||
|
||||
# Tick数据回调类型
|
||||
TickCallback = Callable[[str, TickData], None]
|
||||
|
||||
|
||||
class DataSourceAdapter(ABC):
|
||||
"""数据源适配器接口"""
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self, config: dict) -> None:
|
||||
"""建立连接"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def subscribe_ticks(self, symbols: List[str], callback: TickCallback) -> None:
|
||||
"""订阅实时Tick"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def fetch_klines(
|
||||
self,
|
||||
symbol: str,
|
||||
start: str,
|
||||
end: str,
|
||||
freq: str
|
||||
) -> List[KLineData]:
|
||||
"""拉取历史K线"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def fetch_symbols(self, asset_type: str) -> List[SymbolInfo]:
|
||||
"""获取标的列表"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def fetch_trading_calendar(
|
||||
self,
|
||||
exchange: str,
|
||||
start: str,
|
||||
end: str
|
||||
) -> List[TradeCalData]:
|
||||
"""获取交易日历"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def health_check(self) -> bool:
|
||||
"""健康检查"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def close(self) -> None:
|
||||
"""关闭连接"""
|
||||
pass
|
||||
@ -1,5 +0,0 @@
|
||||
"""API路由模块"""
|
||||
from .routes import router
|
||||
from .admin_routes import admin_router
|
||||
|
||||
__all__ = ["router", "admin_router"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,232 +0,0 @@
|
||||
"""管理后台API路由 - 对应Go的api/admin_router.go"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Header, Query
|
||||
from typing import Optional
|
||||
|
||||
from app.models import (
|
||||
Response, ConfigListRequest, ConfigUpdateRequest,
|
||||
ReloadRequest, AdapterToggleRequest, AdapterConfigUpdateRequest,
|
||||
APITestRequest, WSTestRequest, TestHistoryRequest
|
||||
)
|
||||
from app.services import ConfigService, AdapterService, TestService
|
||||
from app.core.config import get_config
|
||||
|
||||
admin_router = APIRouter()
|
||||
|
||||
# 服务实例
|
||||
config_service = ConfigService()
|
||||
adapter_service = AdapterService()
|
||||
test_service = TestService()
|
||||
|
||||
|
||||
def verify_admin_token(x_admin_token: Optional[str] = Header(None)):
|
||||
"""验证Admin Token"""
|
||||
# TODO: 实现Token验证
|
||||
return x_admin_token
|
||||
|
||||
|
||||
# ============================================
|
||||
# 系统管理接口
|
||||
# ============================================
|
||||
|
||||
@admin_router.get("/admin/system/status", response_model=Response)
|
||||
def get_system_status(
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取系统状态"""
|
||||
try:
|
||||
data = config_service.get_system_status()
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/system/reload", response_model=Response)
|
||||
def reload_config(
|
||||
req: Optional[ReloadRequest] = None,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""热加载配置"""
|
||||
try:
|
||||
if req is None:
|
||||
req = ReloadRequest()
|
||||
data = config_service.reload_config(req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/system/restart", response_model=Response)
|
||||
def restart_service(
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""重启服务"""
|
||||
# TODO: 实现服务重启逻辑
|
||||
return Response(
|
||||
code=0,
|
||||
message="重启命令已发送",
|
||||
data={"status": "restarting"}
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# 配置管理接口
|
||||
# ============================================
|
||||
|
||||
@admin_router.get("/admin/config", response_model=Response)
|
||||
def get_config_list(
|
||||
type: Optional[str] = Query(None, description="配置类型筛选"),
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取配置列表"""
|
||||
try:
|
||||
from app.models import ConfigType
|
||||
req = ConfigListRequest()
|
||||
if type:
|
||||
req.type = ConfigType(type)
|
||||
|
||||
data = config_service.get_config_list(req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.put("/admin/config", response_model=Response)
|
||||
def update_config(
|
||||
req: ConfigUpdateRequest,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""更新配置"""
|
||||
try:
|
||||
data = config_service.update_config(req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/config/reload", response_model=Response)
|
||||
def reload_config_endpoint(
|
||||
req: Optional[ReloadRequest] = None,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""热加载配置"""
|
||||
return reload_config(req, token)
|
||||
|
||||
|
||||
# ============================================
|
||||
# 适配器管理接口
|
||||
# ============================================
|
||||
|
||||
@admin_router.get("/admin/adapters", response_model=Response)
|
||||
def get_adapter_list(
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取适配器列表"""
|
||||
try:
|
||||
data = adapter_service.get_adapter_list()
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/adapters/toggle", response_model=Response)
|
||||
def toggle_adapter(
|
||||
req: AdapterToggleRequest,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""切换适配器状态"""
|
||||
try:
|
||||
adapter_service.toggle_adapter(req)
|
||||
return Response(code=0, message="success")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.put("/admin/adapters/config", response_model=Response)
|
||||
def update_adapter_config(
|
||||
req: AdapterConfigUpdateRequest,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""更新适配器配置"""
|
||||
try:
|
||||
adapter_service.update_adapter_config(req)
|
||||
return Response(code=0, message="success")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# ============================================
|
||||
# 测试管理接口
|
||||
# ============================================
|
||||
|
||||
@admin_router.get("/admin/tests/api", response_model=Response)
|
||||
def get_api_test_list(
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取API测试列表"""
|
||||
try:
|
||||
data = test_service.get_api_test_list()
|
||||
# 设置基础URL
|
||||
config = get_config()
|
||||
data.base_url = f"http://localhost:{config.server.port}"
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/tests/api/run", response_model=Response)
|
||||
async def run_api_test(
|
||||
req: APITestRequest,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""执行API测试"""
|
||||
try:
|
||||
config = get_config()
|
||||
base_url = f"http://localhost:{config.server.port}"
|
||||
data = await test_service.run_api_test(base_url, req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.get("/admin/tests/ws", response_model=Response)
|
||||
def get_ws_test_list(
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取WebSocket测试列表"""
|
||||
try:
|
||||
data = test_service.get_ws_test_list()
|
||||
config = get_config()
|
||||
data.ws_url = f"ws://localhost:{config.server.port}/v1/stream"
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.post("/admin/tests/ws/run", response_model=Response)
|
||||
async def run_ws_test(
|
||||
req: WSTestRequest,
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""执行WebSocket测试"""
|
||||
try:
|
||||
config = get_config()
|
||||
ws_url = f"ws://localhost:{config.server.port}/v1/stream"
|
||||
data = await test_service.run_ws_test(ws_url, req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@admin_router.get("/admin/tests/history", response_model=Response)
|
||||
def get_test_history(
|
||||
type: Optional[str] = Query(None, description="测试类型"),
|
||||
limit: int = Query(default=20, ge=1, le=100, description="数量限制"),
|
||||
token: str = Depends(verify_admin_token)
|
||||
):
|
||||
"""获取测试历史"""
|
||||
try:
|
||||
req = TestHistoryRequest(type=type, limit=limit)
|
||||
data = test_service.get_test_history(req)
|
||||
return Response(code=0, message="success", data=data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,131 +0,0 @@
|
||||
"""配置管理模块"""
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any, Optional
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class ServerConfig(BaseModel):
|
||||
"""服务器配置"""
|
||||
port: int = 8080
|
||||
mode: str = "debug" # debug/release
|
||||
api_key: str = "demo-api-key-2024"
|
||||
|
||||
|
||||
class DatabaseConfig(BaseModel):
|
||||
"""数据库配置"""
|
||||
host: str = "localhost"
|
||||
port: int = 5432
|
||||
user: str = "postgres"
|
||||
password: str = "postgres"
|
||||
database: str = "marketdata"
|
||||
|
||||
@property
|
||||
def database_url(self) -> str:
|
||||
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
|
||||
|
||||
|
||||
class RedisConfig(BaseModel):
|
||||
"""Redis配置"""
|
||||
host: str = "localhost"
|
||||
port: int = 6379
|
||||
password: str = ""
|
||||
db: int = 0
|
||||
|
||||
|
||||
class SourceInfo(BaseModel):
|
||||
"""数据源信息"""
|
||||
type: str = "http"
|
||||
config: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SourceConfig(BaseModel):
|
||||
"""源配置"""
|
||||
active: str = "tushare"
|
||||
list: Dict[str, SourceInfo] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SourcesConfig(BaseModel):
|
||||
"""数据源配置"""
|
||||
stock: SourceConfig = Field(default_factory=lambda: SourceConfig(
|
||||
active="tushare",
|
||||
list={"tushare": SourceInfo(type="http", config={"base_url": "https://api.tushare.pro"})}
|
||||
))
|
||||
futures: SourceConfig = Field(default_factory=lambda: SourceConfig(
|
||||
active="tushare",
|
||||
list={"tushare": SourceInfo(type="http", config={"base_url": "https://api.tushare.pro"})}
|
||||
))
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
"""主配置类"""
|
||||
server: ServerConfig = Field(default_factory=ServerConfig)
|
||||
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
|
||||
redis: RedisConfig = Field(default_factory=RedisConfig)
|
||||
sources: SourcesConfig = Field(default_factory=SourcesConfig)
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""环境变量配置"""
|
||||
port: int = Field(default=8080, alias="PORT")
|
||||
database_url: Optional[str] = Field(default=None, alias="DATABASE_URL")
|
||||
tushare_token: Optional[str] = Field(default=None, alias="TUSHARE_TOKEN")
|
||||
api_key: Optional[str] = Field(default=None, alias="API_KEY")
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
def load_config(config_path: str = "./config.json") -> Config:
|
||||
"""从文件加载配置"""
|
||||
if not os.path.exists(config_path):
|
||||
return Config()
|
||||
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return Config.model_validate(data)
|
||||
|
||||
|
||||
def save_config(config: Config, config_path: str = "./config.json"):
|
||||
"""保存配置到文件"""
|
||||
os.makedirs(os.path.dirname(config_path) or '.', exist_ok=True)
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config.model_dump(), f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
# 全局配置实例
|
||||
_config: Optional[Config] = None
|
||||
|
||||
|
||||
def get_config() -> Config:
|
||||
"""获取当前配置"""
|
||||
global _config
|
||||
if _config is None:
|
||||
_config = load_config()
|
||||
return _config
|
||||
|
||||
|
||||
def set_config(config: Config):
|
||||
"""设置全局配置"""
|
||||
global _config
|
||||
_config = config
|
||||
|
||||
|
||||
def reload_config(config_path: str = "./config.json") -> Config:
|
||||
"""重新加载配置"""
|
||||
global _config
|
||||
_config = load_config(config_path)
|
||||
return _config
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_settings() -> Settings:
|
||||
"""获取环境变量设置"""
|
||||
return Settings()
|
||||
@ -1,78 +0,0 @@
|
||||
"""错误定义模块"""
|
||||
from enum import IntEnum
|
||||
from typing import Optional, Any, Dict
|
||||
|
||||
|
||||
class ErrorCode(IntEnum):
|
||||
"""错误码"""
|
||||
OK = 0
|
||||
BAD_REQUEST = 400
|
||||
UNAUTHORIZED = 401
|
||||
NOT_FOUND = 404
|
||||
RATE_LIMIT = 429
|
||||
INTERNAL = 500
|
||||
|
||||
|
||||
class AppException(Exception):
|
||||
"""应用异常基类"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
code: ErrorCode = ErrorCode.INTERNAL,
|
||||
detail: Optional[str] = None
|
||||
):
|
||||
self.message = message
|
||||
self.code = code
|
||||
self.detail = detail
|
||||
super().__init__(message)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"code": int(self.code),
|
||||
"message": self.message,
|
||||
"detail": self.detail
|
||||
}
|
||||
|
||||
|
||||
# 参数错误
|
||||
class InvalidParamError(AppException):
|
||||
def __init__(self, message: str = "参数错误", detail: Optional[str] = None):
|
||||
super().__init__(message, ErrorCode.BAD_REQUEST, detail)
|
||||
|
||||
|
||||
class InvalidSymbolError(AppException):
|
||||
def __init__(self, message: str = "无效的标的代码"):
|
||||
super().__init__(message, ErrorCode.BAD_REQUEST)
|
||||
|
||||
|
||||
class InvalidDateError(AppException):
|
||||
def __init__(self, message: str = "无效的日期格式"):
|
||||
super().__init__(message, ErrorCode.BAD_REQUEST)
|
||||
|
||||
|
||||
# 数据错误
|
||||
class SymbolNotFoundError(AppException):
|
||||
def __init__(self, message: str = "标的不存在"):
|
||||
super().__init__(message, ErrorCode.NOT_FOUND)
|
||||
|
||||
|
||||
class DataNotFoundError(AppException):
|
||||
def __init__(self, message: str = "数据不存在"):
|
||||
super().__init__(message, ErrorCode.NOT_FOUND)
|
||||
|
||||
|
||||
class DataSourceUnavailableError(AppException):
|
||||
def __init__(self, message: str = "数据源不可用"):
|
||||
super().__init__(message, ErrorCode.INTERNAL)
|
||||
|
||||
|
||||
# 权限错误
|
||||
class UnauthorizedError(AppException):
|
||||
def __init__(self, message: str = "未授权"):
|
||||
super().__init__(message, ErrorCode.UNAUTHORIZED)
|
||||
|
||||
|
||||
class RateLimitError(AppException):
|
||||
def __init__(self, message: str = "请求过于频繁"):
|
||||
super().__init__(message, ErrorCode.RATE_LIMIT)
|
||||
@ -1,47 +0,0 @@
|
||||
"""日志工具模块"""
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def setup_logging(
|
||||
level: int = logging.INFO,
|
||||
format_string: Optional[str] = None
|
||||
) -> logging.Logger:
|
||||
"""设置日志配置"""
|
||||
if format_string is None:
|
||||
format_string = "%(asctime)s | %(levelname)-8s | %(message)s"
|
||||
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format=format_string,
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
return logging.getLogger("market_data")
|
||||
|
||||
|
||||
# 全局logger实例
|
||||
logger = setup_logging()
|
||||
|
||||
|
||||
def info(msg: str, *args, **kwargs):
|
||||
"""信息日志"""
|
||||
logger.info(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def error(msg: str, *args, **kwargs):
|
||||
"""错误日志"""
|
||||
logger.error(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def debug(msg: str, *args, **kwargs):
|
||||
"""调试日志"""
|
||||
logger.debug(msg, *args, **kwargs)
|
||||
|
||||
|
||||
def warning(msg: str, *args, **kwargs):
|
||||
"""警告日志"""
|
||||
logger.warning(msg, *args, **kwargs)
|
||||
@ -1,135 +0,0 @@
|
||||
"""数据模型模块"""
|
||||
from app.adapters.base import TradeCalData
|
||||
from .types import (
|
||||
Frequency,
|
||||
AdjustType,
|
||||
AssetClass,
|
||||
SymbolType,
|
||||
Exchange,
|
||||
DataSourceStatus,
|
||||
KLineItem,
|
||||
KLineData,
|
||||
KLineQueryRequest,
|
||||
BatchKLineRequest,
|
||||
BatchKLineResult,
|
||||
BatchKLineData,
|
||||
KLineSubData,
|
||||
Symbol,
|
||||
SymbolListRequest,
|
||||
SymbolListData,
|
||||
DataSourceInfo,
|
||||
DataSourceStatusData,
|
||||
SourceSwitchRequest,
|
||||
BackfillRequest,
|
||||
TradingDatesRequest,
|
||||
TradingDatesData,
|
||||
FuturesContractsRequest,
|
||||
FuturesContractInfo,
|
||||
FuturesContractsData,
|
||||
Response,
|
||||
ErrorResponse,
|
||||
SuccessResponse,
|
||||
HealthResponse,
|
||||
)
|
||||
from .admin_types import (
|
||||
ConfigType,
|
||||
ConfigItem,
|
||||
ConfigSection,
|
||||
ConfigListRequest,
|
||||
ConfigListData,
|
||||
ConfigUpdateRequest,
|
||||
ConfigUpdateData,
|
||||
AdapterInfo,
|
||||
AdapterStatus,
|
||||
AdapterListData,
|
||||
AdapterToggleRequest,
|
||||
AdapterConfigUpdateRequest,
|
||||
SystemStatusData,
|
||||
MemoryInfo,
|
||||
RestartRequest,
|
||||
ReloadRequest,
|
||||
ReloadData,
|
||||
APITestCase,
|
||||
APITestCategory,
|
||||
APITestListData,
|
||||
APITestRequest,
|
||||
APITestResult,
|
||||
WSTestCase,
|
||||
WSTestListData,
|
||||
WSTestRequest,
|
||||
WSTestResult,
|
||||
WSMessage,
|
||||
TestHistoryRequest,
|
||||
TestHistoryData,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# 数据适配器基础类型
|
||||
"TradeCalData",
|
||||
# 基础类型
|
||||
"Frequency",
|
||||
"AdjustType",
|
||||
"AssetClass",
|
||||
"SymbolType",
|
||||
"Exchange",
|
||||
"DataSourceStatus",
|
||||
# K线数据
|
||||
"KLineItem",
|
||||
"KLineData",
|
||||
"KLineQueryRequest",
|
||||
"BatchKLineRequest",
|
||||
"BatchKLineResult",
|
||||
"BatchKLineData",
|
||||
"KLineSubData",
|
||||
# 标的
|
||||
"Symbol",
|
||||
"SymbolListRequest",
|
||||
"SymbolListData",
|
||||
# 数据源
|
||||
"DataSourceInfo",
|
||||
"DataSourceStatusData",
|
||||
"SourceSwitchRequest",
|
||||
"BackfillRequest",
|
||||
# 交易日历
|
||||
"TradingDatesRequest",
|
||||
"TradingDatesData",
|
||||
# 期货
|
||||
"FuturesContractsRequest",
|
||||
"FuturesContractInfo",
|
||||
"FuturesContractsData",
|
||||
# 响应
|
||||
"Response",
|
||||
"ErrorResponse",
|
||||
"SuccessResponse",
|
||||
"HealthResponse",
|
||||
# 管理后台
|
||||
"ConfigType",
|
||||
"ConfigItem",
|
||||
"ConfigSection",
|
||||
"ConfigListRequest",
|
||||
"ConfigListData",
|
||||
"ConfigUpdateRequest",
|
||||
"ConfigUpdateData",
|
||||
"AdapterInfo",
|
||||
"AdapterStatus",
|
||||
"AdapterListData",
|
||||
"AdapterToggleRequest",
|
||||
"AdapterConfigUpdateRequest",
|
||||
"SystemStatusData",
|
||||
"MemoryInfo",
|
||||
"RestartRequest",
|
||||
"ReloadRequest",
|
||||
"ReloadData",
|
||||
"APITestCase",
|
||||
"APITestCategory",
|
||||
"APITestListData",
|
||||
"APITestRequest",
|
||||
"APITestResult",
|
||||
"WSTestCase",
|
||||
"WSTestListData",
|
||||
"WSTestRequest",
|
||||
"WSTestResult",
|
||||
"WSMessage",
|
||||
"TestHistoryRequest",
|
||||
"TestHistoryData",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,250 +0,0 @@
|
||||
"""管理后台类型定义 - 对应Go的api/admin_types.go"""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Any, Literal
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ============================================
|
||||
# 配置管理类型
|
||||
# ============================================
|
||||
|
||||
class ConfigType(str, Enum):
|
||||
"""配置类型"""
|
||||
SERVER = "server"
|
||||
DATABASE = "database"
|
||||
REDIS = "redis"
|
||||
SOURCE = "source"
|
||||
MONITOR = "monitor"
|
||||
LOG = "log"
|
||||
|
||||
|
||||
class ConfigItem(BaseModel):
|
||||
"""配置项"""
|
||||
key: str = Field(..., description="配置键")
|
||||
value: Any = Field(..., description="配置值")
|
||||
type: str = Field(..., description="值类型: string/int/bool/json")
|
||||
description: str = Field(..., description="配置说明")
|
||||
editable: bool = Field(default=True, description="是否可编辑")
|
||||
required: bool = Field(default=True, description="是否必填")
|
||||
|
||||
|
||||
class ConfigSection(BaseModel):
|
||||
"""配置分组"""
|
||||
name: str = Field(..., description="分组名称")
|
||||
type: ConfigType = Field(..., description="分组类型")
|
||||
description: str = Field(..., description="分组说明")
|
||||
items: List[ConfigItem] = Field(default_factory=list, description="配置项列表")
|
||||
|
||||
|
||||
class ConfigListRequest(BaseModel):
|
||||
"""获取配置列表请求"""
|
||||
type: Optional[ConfigType] = Field(None, description="配置类型筛选")
|
||||
|
||||
|
||||
class ConfigListData(BaseModel):
|
||||
"""配置列表响应"""
|
||||
sections: List[ConfigSection] = Field(default_factory=list, description="配置分组列表")
|
||||
version: str = Field(default="1.0.0", description="配置版本")
|
||||
updated: datetime = Field(default_factory=datetime.now, description="最后更新时间")
|
||||
|
||||
|
||||
class ConfigUpdateRequest(BaseModel):
|
||||
"""更新配置请求"""
|
||||
type: ConfigType = Field(..., description="配置类型")
|
||||
items: Dict[str, Any] = Field(..., description="更新的配置项")
|
||||
|
||||
|
||||
class ConfigUpdateData(BaseModel):
|
||||
"""更新配置响应"""
|
||||
success: bool = Field(..., description="是否成功")
|
||||
need_restart: bool = Field(default=False, description="是否需要重启")
|
||||
message: str = Field(..., description="提示信息")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 适配器管理类型
|
||||
# ============================================
|
||||
|
||||
class AdapterStatus(str, Enum):
|
||||
"""适配器状态"""
|
||||
ACTIVE = "active"
|
||||
STANDBY = "standby"
|
||||
DISABLED = "disabled"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class AdapterInfo(BaseModel):
|
||||
"""适配器信息"""
|
||||
name: str = Field(..., description="适配器名称")
|
||||
type: str = Field(..., description="适配器类型")
|
||||
version: str = Field(..., description="版本")
|
||||
description: str = Field(..., description="描述")
|
||||
status: AdapterStatus = Field(..., description="状态")
|
||||
config: Dict[str, str] = Field(default_factory=dict, description="当前配置")
|
||||
last_error: Optional[str] = Field(None, description="最后错误")
|
||||
updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
|
||||
|
||||
|
||||
class AdapterListData(BaseModel):
|
||||
"""适配器列表响应"""
|
||||
adapters: List[AdapterInfo] = Field(default_factory=list, description="适配器列表")
|
||||
|
||||
|
||||
class AdapterToggleRequest(BaseModel):
|
||||
"""启用/禁用适配器请求"""
|
||||
name: str = Field(..., description="适配器名称")
|
||||
enable: bool = Field(..., description="是否启用")
|
||||
|
||||
|
||||
class AdapterConfigUpdateRequest(BaseModel):
|
||||
"""更新适配器配置请求"""
|
||||
name: str = Field(..., description="适配器名称")
|
||||
config: Dict[str, str] = Field(..., description="配置")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 系统管理类型
|
||||
# ============================================
|
||||
|
||||
class MemoryInfo(BaseModel):
|
||||
"""内存信息"""
|
||||
alloc: int = Field(..., description="已分配内存")
|
||||
total_alloc: int = Field(..., description="累计分配")
|
||||
sys: int = Field(..., description="系统内存")
|
||||
num_gc: int = Field(..., description="GC次数")
|
||||
|
||||
|
||||
class SystemStatusData(BaseModel):
|
||||
"""系统状态数据"""
|
||||
status: str = Field(..., description="系统状态")
|
||||
version: str = Field(..., description="系统版本")
|
||||
start_time: datetime = Field(..., description="启动时间")
|
||||
uptime: str = Field(..., description="运行时长")
|
||||
python_version: str = Field(..., description="Python版本")
|
||||
memory: MemoryInfo = Field(..., description="内存使用")
|
||||
threads: int = Field(..., description="线程数量")
|
||||
|
||||
|
||||
class RestartRequest(BaseModel):
|
||||
"""重启服务请求"""
|
||||
force: bool = Field(default=False, description="是否强制重启")
|
||||
|
||||
|
||||
class ReloadRequest(BaseModel):
|
||||
"""热加载配置请求"""
|
||||
config_type: Optional[ConfigType] = Field(None, description="指定配置类型")
|
||||
|
||||
|
||||
class ReloadData(BaseModel):
|
||||
"""热加载响应"""
|
||||
success: bool = Field(..., description="是否成功")
|
||||
message: str = Field(..., description="提示信息")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 接口测试类型
|
||||
# ============================================
|
||||
|
||||
class APITestCase(BaseModel):
|
||||
"""接口测试用例"""
|
||||
id: str = Field(..., description="用例ID")
|
||||
name: str = Field(..., description="用例名称")
|
||||
method: str = Field(..., description="HTTP方法")
|
||||
path: str = Field(..., description="请求路径")
|
||||
description: str = Field(..., description="描述")
|
||||
params: Dict[str, str] = Field(default_factory=dict, description="默认参数")
|
||||
body: Optional[Any] = Field(None, description="请求体")
|
||||
|
||||
|
||||
class APITestCategory(BaseModel):
|
||||
"""测试分类"""
|
||||
name: str = Field(..., description="分类名称")
|
||||
items: List[APITestCase] = Field(default_factory=list, description="测试用例")
|
||||
|
||||
|
||||
class APITestListData(BaseModel):
|
||||
"""接口测试列表响应"""
|
||||
categories: List[APITestCategory] = Field(default_factory=list, description="分类列表")
|
||||
base_url: str = Field(default="", description="基础URL")
|
||||
|
||||
|
||||
class APITestRequest(BaseModel):
|
||||
"""执行接口测试请求"""
|
||||
id: str = Field(..., description="用例ID")
|
||||
params: Dict[str, str] = Field(default_factory=dict, description="自定义参数")
|
||||
body: Optional[Any] = Field(None, description="自定义请求体")
|
||||
|
||||
|
||||
class APITestResult(BaseModel):
|
||||
"""接口测试结果"""
|
||||
id: int = Field(..., description="测试ID")
|
||||
case_id: str = Field(..., description="用例ID")
|
||||
name: str = Field(..., description="用例名称")
|
||||
success: bool = Field(..., description="是否成功")
|
||||
status_code: int = Field(0, description="HTTP状态码")
|
||||
latency: int = Field(..., description="延迟(ms)")
|
||||
request: Any = Field(None, description="请求信息")
|
||||
response: Any = Field(None, description="响应信息")
|
||||
error: Optional[str] = Field(None, description="错误信息")
|
||||
timestamp: datetime = Field(default_factory=datetime.now, description="测试时间")
|
||||
|
||||
|
||||
# ============================================
|
||||
# WebSocket测试类型
|
||||
# ============================================
|
||||
|
||||
class WSTestCase(BaseModel):
|
||||
"""WebSocket测试用例"""
|
||||
id: str = Field(..., description="用例ID")
|
||||
name: str = Field(..., description="用例名称")
|
||||
description: str = Field(..., description="描述")
|
||||
action: str = Field(..., description="动作类型")
|
||||
symbols: List[str] = Field(default_factory=list, description="订阅标的")
|
||||
|
||||
|
||||
class WSTestListData(BaseModel):
|
||||
"""WebSocket测试列表响应"""
|
||||
cases: List[WSTestCase] = Field(default_factory=list, description="测试用例")
|
||||
ws_url: str = Field(default="", description="WebSocket地址")
|
||||
|
||||
|
||||
class WSTestRequest(BaseModel):
|
||||
"""WebSocket测试请求"""
|
||||
id: str = Field(..., description="用例ID")
|
||||
symbols: List[str] = Field(default_factory=list, description="自定义标的")
|
||||
|
||||
|
||||
class WSMessage(BaseModel):
|
||||
"""WebSocket消息"""
|
||||
type: str = Field(..., description="消息类型")
|
||||
data: Any = Field(None, description="消息内容")
|
||||
timestamp: datetime = Field(default_factory=datetime.now, description="时间")
|
||||
|
||||
|
||||
class WSTestResult(BaseModel):
|
||||
"""WebSocket测试结果"""
|
||||
id: str = Field(..., description="测试ID")
|
||||
case_id: str = Field(..., description="用例ID")
|
||||
success: bool = Field(..., description="是否成功")
|
||||
latency: int = Field(..., description="连接延迟(ms)")
|
||||
messages: List[WSMessage] = Field(default_factory=list, description="收到的消息")
|
||||
error: Optional[str] = Field(None, description="错误信息")
|
||||
timestamp: datetime = Field(default_factory=datetime.now, description="测试时间")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 测试历史记录类型
|
||||
# ============================================
|
||||
|
||||
class TestHistoryRequest(BaseModel):
|
||||
"""获取测试历史请求"""
|
||||
type: Optional[str] = Field(None, description="测试类型: api/ws")
|
||||
limit: int = Field(default=20, ge=1, le=100, description="数量限制")
|
||||
|
||||
|
||||
class TestHistoryData(BaseModel):
|
||||
"""测试历史数据"""
|
||||
api_tests: List[APITestResult] = Field(default_factory=list, description="API测试历史")
|
||||
ws_tests: List[WSTestResult] = Field(default_factory=list, description="WebSocket测试历史")
|
||||
@ -1,4 +0,0 @@
|
||||
"""数据质量监控模块"""
|
||||
from .monitor import DataQualityMonitor, AlertSender, LogAlertSender
|
||||
|
||||
__all__ = ["DataQualityMonitor", "AlertSender", "LogAlertSender"]
|
||||
@ -1,13 +0,0 @@
|
||||
"""数据访问层模块"""
|
||||
from .database import get_db, SessionLocal, engine, Base
|
||||
from .stock_repository import StockRepository
|
||||
from .futures_repository import FuturesRepository
|
||||
|
||||
__all__ = [
|
||||
"get_db",
|
||||
"SessionLocal",
|
||||
"engine",
|
||||
"Base",
|
||||
"StockRepository",
|
||||
"FuturesRepository",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,268 +0,0 @@
|
||||
"""期货数据仓库"""
|
||||
from datetime import datetime
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, or_
|
||||
|
||||
from app.models import (
|
||||
KLineItem, Symbol, SymbolListRequest, SymbolListData,
|
||||
TradingDatesData, TradeCalData, Frequency,
|
||||
FuturesContractsData, FuturesContractInfo
|
||||
)
|
||||
from app.repositories.models import (
|
||||
FuturesSymbol, FuturesKLine1M, FuturesKLine1D,
|
||||
FuturesTradingCalendar
|
||||
)
|
||||
|
||||
|
||||
class FuturesRepository:
|
||||
"""期货数据仓库"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_klines(
|
||||
self,
|
||||
symbol: str,
|
||||
freq: Frequency,
|
||||
start: datetime,
|
||||
end: datetime
|
||||
) -> List[KLineItem]:
|
||||
"""获取K线数据"""
|
||||
kline_model = self._get_kline_model(freq)
|
||||
|
||||
query = self.db.query(kline_model).filter(
|
||||
kline_model.symbol_id == symbol,
|
||||
kline_model.ts >= start,
|
||||
kline_model.ts <= end
|
||||
).order_by(kline_model.ts.asc())
|
||||
|
||||
results = query.all()
|
||||
|
||||
items = []
|
||||
for r in results:
|
||||
item = KLineItem(
|
||||
time=r.ts,
|
||||
open=float(r.open),
|
||||
high=float(r.high),
|
||||
low=float(r.low),
|
||||
close=float(r.close),
|
||||
volume=r.volume,
|
||||
amount=float(r.amount),
|
||||
open_interest=r.open_interest
|
||||
)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def _get_kline_model(self, freq: Frequency):
|
||||
"""根据周期获取K线模型"""
|
||||
mapping = {
|
||||
Frequency.FREQ_1M: FuturesKLine1M,
|
||||
Frequency.FREQ_1D: FuturesKLine1D,
|
||||
}
|
||||
return mapping.get(freq, FuturesKLine1D)
|
||||
|
||||
def save_klines(
|
||||
self,
|
||||
freq: Frequency,
|
||||
symbol: str,
|
||||
items: List[KLineItem]
|
||||
) -> None:
|
||||
"""保存K线数据"""
|
||||
if not items:
|
||||
return
|
||||
|
||||
kline_model = self._get_kline_model(freq)
|
||||
|
||||
for item in items:
|
||||
existing = self.db.query(kline_model).filter(
|
||||
kline_model.symbol_id == symbol,
|
||||
kline_model.ts == item.time
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.open = item.open
|
||||
existing.high = item.high
|
||||
existing.low = item.low
|
||||
existing.close = item.close
|
||||
existing.volume = item.volume
|
||||
existing.amount = item.amount
|
||||
existing.open_interest = item.open_interest
|
||||
else:
|
||||
new_record = kline_model(
|
||||
symbol_id=symbol,
|
||||
ts=item.time,
|
||||
open=item.open,
|
||||
high=item.high,
|
||||
low=item.low,
|
||||
close=item.close,
|
||||
volume=item.volume,
|
||||
amount=item.amount,
|
||||
open_interest=item.open_interest
|
||||
)
|
||||
self.db.add(new_record)
|
||||
|
||||
self.db.commit()
|
||||
|
||||
def list_symbols(
|
||||
self,
|
||||
req: SymbolListRequest
|
||||
) -> Tuple[List[Symbol], int]:
|
||||
"""查询标的列表"""
|
||||
query = self.db.query(FuturesSymbol)
|
||||
|
||||
# 筛选条件
|
||||
if req.exchange:
|
||||
query = query.filter(FuturesSymbol.exchange == req.exchange.value)
|
||||
|
||||
if req.underlying:
|
||||
query = query.filter(FuturesSymbol.underlying == req.underlying)
|
||||
|
||||
if req.keyword:
|
||||
keyword = f"%{req.keyword}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
FuturesSymbol.symbol_id.ilike(keyword),
|
||||
FuturesSymbol.name.ilike(keyword)
|
||||
)
|
||||
)
|
||||
|
||||
# 查询总数
|
||||
total = query.count()
|
||||
|
||||
# 分页查询
|
||||
results = query.order_by(FuturesSymbol.symbol_id).offset(
|
||||
(req.page - 1) * req.size
|
||||
).limit(req.size).all()
|
||||
|
||||
symbols = []
|
||||
for r in results:
|
||||
s = Symbol(
|
||||
symbol_id=r.symbol_id,
|
||||
symbol_type=r.symbol_type,
|
||||
exchange=r.exchange,
|
||||
name=r.name,
|
||||
underlying=r.underlying,
|
||||
contract_month=r.contract_month,
|
||||
list_date=r.list_date,
|
||||
delist_date=r.delist_date,
|
||||
status=r.status
|
||||
)
|
||||
symbols.append(s)
|
||||
|
||||
return symbols, total
|
||||
|
||||
def get_trading_dates(self, start: str, end: str) -> TradingDatesData:
|
||||
"""获取交易日历"""
|
||||
results = self.db.query(FuturesTradingCalendar).filter(
|
||||
FuturesTradingCalendar.trade_date >= start,
|
||||
FuturesTradingCalendar.trade_date <= end,
|
||||
FuturesTradingCalendar.is_trading_day == True
|
||||
).order_by(FuturesTradingCalendar.trade_date.asc()).all()
|
||||
|
||||
dates = [r.trade_date for r in results]
|
||||
|
||||
# 计算总天数
|
||||
start_date = datetime.strptime(start, "%Y%m%d")
|
||||
end_date = datetime.strptime(end, "%Y%m%d")
|
||||
total_days = (end_date - start_date).days + 1
|
||||
|
||||
return TradingDatesData(
|
||||
start=start,
|
||||
end=end,
|
||||
total_days=total_days,
|
||||
trading_days=len(dates),
|
||||
trading_dates=dates
|
||||
)
|
||||
|
||||
def get_contracts_by_underlying(
|
||||
self,
|
||||
underlying: str,
|
||||
exchange: Optional[str] = None
|
||||
) -> FuturesContractsData:
|
||||
"""根据品种获取合约"""
|
||||
query = self.db.query(FuturesSymbol).filter(
|
||||
FuturesSymbol.underlying == underlying,
|
||||
FuturesSymbol.status == "active"
|
||||
)
|
||||
|
||||
if exchange:
|
||||
query = query.filter(FuturesSymbol.exchange == exchange)
|
||||
|
||||
results = query.order_by(FuturesSymbol.contract_month.asc()).all()
|
||||
|
||||
contracts = []
|
||||
for r in results:
|
||||
c = FuturesContractInfo(
|
||||
symbol_id=r.symbol_id,
|
||||
exchange=r.exchange,
|
||||
name=r.name,
|
||||
underlying=r.underlying,
|
||||
contract_month=r.contract_month,
|
||||
list_date=r.list_date,
|
||||
delist_date=r.delist_date,
|
||||
status=r.status
|
||||
)
|
||||
contracts.append(c)
|
||||
|
||||
return FuturesContractsData(
|
||||
underlying=underlying,
|
||||
count=len(contracts),
|
||||
items=contracts
|
||||
)
|
||||
|
||||
def save_symbols(self, symbols: List[Symbol]) -> None:
|
||||
"""保存标的列表"""
|
||||
for s in symbols:
|
||||
existing = self.db.query(FuturesSymbol).filter(
|
||||
FuturesSymbol.symbol_id == s.symbol_id
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.name = s.name
|
||||
existing.underlying = s.underlying
|
||||
existing.contract_month = s.contract_month
|
||||
existing.list_date = s.list_date
|
||||
existing.delist_date = s.delist_date
|
||||
existing.status = s.status
|
||||
else:
|
||||
new_symbol = FuturesSymbol(
|
||||
symbol_id=s.symbol_id,
|
||||
symbol_type=s.symbol_type.value if s.symbol_type else "futures",
|
||||
exchange=s.exchange.value if s.exchange else "",
|
||||
name=s.name,
|
||||
underlying=s.underlying or "",
|
||||
contract_month=s.contract_month or "",
|
||||
list_date=s.list_date,
|
||||
delist_date=s.delist_date,
|
||||
status=s.status
|
||||
)
|
||||
self.db.add(new_symbol)
|
||||
|
||||
self.db.commit()
|
||||
|
||||
def save_trading_calendar(self, dates: List[TradeCalData]) -> None:
|
||||
"""保存交易日历"""
|
||||
for d in dates:
|
||||
date_str = d.date.strftime("%Y%m%d")
|
||||
|
||||
existing = self.db.query(FuturesTradingCalendar).filter(
|
||||
FuturesTradingCalendar.trade_date == date_str
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.is_trading_day = d.is_trading_day
|
||||
existing.has_night_session = d.has_night_session
|
||||
existing.week_day = d.date.weekday() + 1
|
||||
else:
|
||||
new_cal = FuturesTradingCalendar(
|
||||
trade_date=date_str,
|
||||
is_trading_day=d.is_trading_day,
|
||||
has_night_session=d.has_night_session,
|
||||
week_day=d.date.weekday() + 1
|
||||
)
|
||||
self.db.add(new_cal)
|
||||
|
||||
self.db.commit()
|
||||
@ -1,214 +0,0 @@
|
||||
"""数据库模型定义"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Float, DateTime,
|
||||
Boolean, Numeric, BigInteger, Index
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
from app.repositories.database import Base
|
||||
|
||||
|
||||
# ============================================
|
||||
# 股票相关表
|
||||
# ============================================
|
||||
|
||||
class StockSymbol(Base):
|
||||
"""股票标的表"""
|
||||
__tablename__ = "symbols"
|
||||
__table_args__ = {"schema": "stock"}
|
||||
|
||||
symbol_id = Column(String(20), primary_key=True, index=True, comment="标的代码")
|
||||
symbol_type = Column(String(20), nullable=False, comment="标的类型")
|
||||
exchange = Column(String(10), nullable=False, index=True, comment="交易所")
|
||||
name = Column(String(100), nullable=False, comment="名称")
|
||||
name_en = Column(String(100), nullable=True, comment="英文名称")
|
||||
list_date = Column(DateTime, nullable=True, comment="上市日期")
|
||||
delist_date = Column(DateTime, nullable=True, comment="退市日期")
|
||||
industry = Column(String(50), nullable=True, comment="行业分类")
|
||||
status = Column(String(20), nullable=False, default="active", comment="状态")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
|
||||
|
||||
class StockTradingCalendar(Base):
|
||||
"""股票交易日历表"""
|
||||
__tablename__ = "trading_calendar"
|
||||
__table_args__ = {"schema": "stock"}
|
||||
|
||||
trade_date = Column(String(8), primary_key=True, comment="交易日期")
|
||||
is_trading_day = Column(Boolean, nullable=False, comment="是否交易日")
|
||||
week_day = Column(Integer, nullable=True, comment="星期几")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
|
||||
|
||||
class StockKLine1M(Base):
|
||||
"""股票1分钟K线"""
|
||||
__tablename__ = "klines_1m"
|
||||
__table_args__ = (
|
||||
Index("idx_stock_1m_symbol_ts", "symbol_id", "ts"),
|
||||
{"schema": "stock"}
|
||||
)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="标的代码")
|
||||
ts = Column(DateTime, nullable=False, comment="时间戳")
|
||||
open = Column(Numeric(18, 4), nullable=False, comment="开盘价")
|
||||
high = Column(Numeric(18, 4), nullable=False, comment="最高价")
|
||||
low = Column(Numeric(18, 4), nullable=False, comment="最低价")
|
||||
close = Column(Numeric(18, 4), nullable=False, comment="收盘价")
|
||||
volume = Column(BigInteger, nullable=False, comment="成交量")
|
||||
amount = Column(Numeric(20, 4), nullable=False, comment="成交额")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
|
||||
|
||||
class StockKLine5M(Base):
|
||||
"""股票5分钟K线"""
|
||||
__tablename__ = "klines_5m"
|
||||
__table_args__ = (
|
||||
Index("idx_stock_5m_symbol_ts", "symbol_id", "ts"),
|
||||
{"schema": "stock"}
|
||||
)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="标的代码")
|
||||
ts = Column(DateTime, nullable=False, comment="时间戳")
|
||||
open = Column(Numeric(18, 4), nullable=False, comment="开盘价")
|
||||
high = Column(Numeric(18, 4), nullable=False, comment="最高价")
|
||||
low = Column(Numeric(18, 4), nullable=False, comment="最低价")
|
||||
close = Column(Numeric(18, 4), nullable=False, comment="收盘价")
|
||||
volume = Column(BigInteger, nullable=False, comment="成交量")
|
||||
amount = Column(Numeric(20, 4), nullable=False, comment="成交额")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
|
||||
|
||||
class StockKLine1D(Base):
|
||||
"""股票日线K线"""
|
||||
__tablename__ = "klines_1d"
|
||||
__table_args__ = (
|
||||
Index("idx_stock_1d_symbol_ts", "symbol_id", "ts"),
|
||||
{"schema": "stock"}
|
||||
)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="标的代码")
|
||||
ts = Column(DateTime, nullable=False, comment="时间戳")
|
||||
open = Column(Numeric(18, 4), nullable=False, comment="开盘价")
|
||||
high = Column(Numeric(18, 4), nullable=False, comment="最高价")
|
||||
low = Column(Numeric(18, 4), nullable=False, comment="最低价")
|
||||
close = Column(Numeric(18, 4), nullable=False, comment="收盘价")
|
||||
volume = Column(BigInteger, nullable=False, comment="成交量")
|
||||
amount = Column(Numeric(20, 4), nullable=False, comment="成交额")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 期货相关表
|
||||
# ============================================
|
||||
|
||||
class FuturesSymbol(Base):
|
||||
"""期货合约表"""
|
||||
__tablename__ = "symbols"
|
||||
__table_args__ = {"schema": "futures"}
|
||||
|
||||
symbol_id = Column(String(20), primary_key=True, index=True, comment="合约代码")
|
||||
symbol_type = Column(String(20), nullable=False, comment="标的类型")
|
||||
exchange = Column(String(10), nullable=False, index=True, comment="交易所")
|
||||
name = Column(String(100), nullable=False, comment="名称")
|
||||
underlying = Column(String(10), nullable=False, index=True, comment="品种代码")
|
||||
contract_month = Column(String(6), nullable=False, comment="合约月份")
|
||||
list_date = Column(DateTime, nullable=True, comment="上市日期")
|
||||
delist_date = Column(DateTime, nullable=True, comment="退市日期")
|
||||
status = Column(String(20), nullable=False, default="active", comment="状态")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
|
||||
|
||||
class FuturesTradingCalendar(Base):
|
||||
"""期货交易日历表"""
|
||||
__tablename__ = "trading_calendar"
|
||||
__table_args__ = {"schema": "futures"}
|
||||
|
||||
trade_date = Column(String(8), primary_key=True, comment="交易日期")
|
||||
is_trading_day = Column(Boolean, nullable=False, comment="是否交易日")
|
||||
has_night_session = Column(Boolean, default=False, comment="是否有夜盘")
|
||||
week_day = Column(Integer, nullable=True, comment="星期几")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
|
||||
|
||||
class FuturesKLine1M(Base):
|
||||
"""期货1分钟K线"""
|
||||
__tablename__ = "klines_1m"
|
||||
__table_args__ = (
|
||||
Index("idx_futures_1m_symbol_ts", "symbol_id", "ts"),
|
||||
{"schema": "futures"}
|
||||
)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="合约代码")
|
||||
ts = Column(DateTime, nullable=False, comment="时间戳")
|
||||
open = Column(Numeric(18, 4), nullable=False, comment="开盘价")
|
||||
high = Column(Numeric(18, 4), nullable=False, comment="最高价")
|
||||
low = Column(Numeric(18, 4), nullable=False, comment="最低价")
|
||||
close = Column(Numeric(18, 4), nullable=False, comment="收盘价")
|
||||
volume = Column(BigInteger, nullable=False, comment="成交量")
|
||||
amount = Column(Numeric(20, 4), nullable=False, comment="成交额")
|
||||
open_interest = Column(BigInteger, nullable=True, comment="持仓量")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
|
||||
|
||||
class FuturesKLine1D(Base):
|
||||
"""期货日线K线"""
|
||||
__tablename__ = "klines_1d"
|
||||
__table_args__ = (
|
||||
Index("idx_futures_1d_symbol_ts", "symbol_id", "ts"),
|
||||
{"schema": "futures"}
|
||||
)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="合约代码")
|
||||
ts = Column(DateTime, nullable=False, comment="时间戳")
|
||||
open = Column(Numeric(18, 4), nullable=False, comment="开盘价")
|
||||
high = Column(Numeric(18, 4), nullable=False, comment="最高价")
|
||||
low = Column(Numeric(18, 4), nullable=False, comment="最低价")
|
||||
close = Column(Numeric(18, 4), nullable=False, comment="收盘价")
|
||||
volume = Column(BigInteger, nullable=False, comment="成交量")
|
||||
amount = Column(Numeric(20, 4), nullable=False, comment="成交额")
|
||||
open_interest = Column(BigInteger, nullable=True, comment="持仓量")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 公共表
|
||||
# ============================================
|
||||
|
||||
class DataSourceConfig(Base):
|
||||
"""数据源配置表"""
|
||||
__tablename__ = "data_source_config"
|
||||
__table_args__ = {"schema": "public"}
|
||||
|
||||
asset_class = Column(String(20), primary_key=True, comment="资产类别")
|
||||
active_source = Column(String(50), nullable=False, comment="当前激活源")
|
||||
standby_sources = Column(ARRAY(String), nullable=True, comment="待命源列表")
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
|
||||
|
||||
class DataQualityCheck(Base):
|
||||
"""数据质量检查表"""
|
||||
__tablename__ = "data_quality_checks"
|
||||
__table_args__ = {"schema": "stock"} # 也可以是futures
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
check_date = Column(String(8), nullable=False, index=True, comment="检查日期")
|
||||
symbol_id = Column(String(20), nullable=False, index=True, comment="标的代码")
|
||||
freq = Column(String(10), nullable=False, comment="周期")
|
||||
check_type = Column(String(20), nullable=False, comment="检查类型")
|
||||
status = Column(String(10), nullable=False, comment="状态 pass/fail")
|
||||
expect_count = Column(Integer, nullable=True, comment="期望数量")
|
||||
actual_count = Column(Integer, nullable=True, comment="实际数量")
|
||||
detail = Column(String(500), nullable=True, comment="详情")
|
||||
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
@ -1,222 +0,0 @@
|
||||
"""股票数据仓库"""
|
||||
from datetime import datetime, time
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, or_
|
||||
|
||||
from app.models import (
|
||||
KLineItem, Symbol, SymbolListRequest, SymbolListData,
|
||||
TradingDatesData, TradeCalData, AdjustType, Frequency
|
||||
)
|
||||
from app.repositories.models import (
|
||||
StockSymbol, StockKLine1M, StockKLine5M, StockKLine1D,
|
||||
StockTradingCalendar
|
||||
)
|
||||
|
||||
|
||||
class StockRepository:
|
||||
"""股票数据仓库"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_klines(
|
||||
self,
|
||||
symbol: str,
|
||||
freq: Frequency,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
adjust: AdjustType = AdjustType.NONE
|
||||
) -> List[KLineItem]:
|
||||
"""获取K线数据"""
|
||||
# 根据周期选择表
|
||||
kline_model = self._get_kline_model(freq)
|
||||
|
||||
query = self.db.query(kline_model).filter(
|
||||
kline_model.symbol_id == symbol,
|
||||
kline_model.ts >= start,
|
||||
kline_model.ts <= end
|
||||
).order_by(kline_model.ts.asc())
|
||||
|
||||
results = query.all()
|
||||
|
||||
items = []
|
||||
for r in results:
|
||||
item = KLineItem(
|
||||
time=r.ts,
|
||||
open=float(r.open),
|
||||
high=float(r.high),
|
||||
low=float(r.low),
|
||||
close=float(r.close),
|
||||
volume=r.volume,
|
||||
amount=float(r.amount)
|
||||
)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def _get_kline_model(self, freq: Frequency):
|
||||
"""根据周期获取K线模型"""
|
||||
mapping = {
|
||||
Frequency.FREQ_1M: StockKLine1M,
|
||||
Frequency.FREQ_5M: StockKLine5M,
|
||||
Frequency.FREQ_1D: StockKLine1D,
|
||||
}
|
||||
return mapping.get(freq, StockKLine1D)
|
||||
|
||||
def save_klines(self, freq: Frequency, items: List[KLineItem]) -> None:
|
||||
"""保存K线数据"""
|
||||
if not items:
|
||||
return
|
||||
|
||||
kline_model = self._get_kline_model(freq)
|
||||
|
||||
for item in items:
|
||||
# 使用upsert逻辑
|
||||
existing = self.db.query(kline_model).filter(
|
||||
kline_model.symbol_id == getattr(item, 'symbol', ''),
|
||||
kline_model.ts == item.time
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.open = item.open
|
||||
existing.high = item.high
|
||||
existing.low = item.low
|
||||
existing.close = item.close
|
||||
existing.volume = item.volume
|
||||
existing.amount = item.amount
|
||||
else:
|
||||
new_record = kline_model(
|
||||
symbol_id=getattr(item, 'symbol', ''),
|
||||
ts=item.time,
|
||||
open=item.open,
|
||||
high=item.high,
|
||||
low=item.low,
|
||||
close=item.close,
|
||||
volume=item.volume,
|
||||
amount=item.amount
|
||||
)
|
||||
self.db.add(new_record)
|
||||
|
||||
self.db.commit()
|
||||
|
||||
def list_symbols(
|
||||
self,
|
||||
req: SymbolListRequest
|
||||
) -> Tuple[List[Symbol], int]:
|
||||
"""查询标的列表"""
|
||||
query = self.db.query(StockSymbol)
|
||||
|
||||
# 筛选条件
|
||||
if req.exchange:
|
||||
query = query.filter(StockSymbol.exchange == req.exchange.value)
|
||||
|
||||
if req.keyword:
|
||||
keyword = f"%{req.keyword}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
StockSymbol.symbol_id.ilike(keyword),
|
||||
StockSymbol.name.ilike(keyword)
|
||||
)
|
||||
)
|
||||
|
||||
# 查询总数
|
||||
total = query.count()
|
||||
|
||||
# 分页查询
|
||||
results = query.order_by(StockSymbol.symbol_id).offset(
|
||||
(req.page - 1) * req.size
|
||||
).limit(req.size).all()
|
||||
|
||||
symbols = []
|
||||
for r in results:
|
||||
s = Symbol(
|
||||
symbol_id=r.symbol_id,
|
||||
symbol_type=r.symbol_type,
|
||||
exchange=r.exchange,
|
||||
name=r.name,
|
||||
name_en=r.name_en,
|
||||
list_date=r.list_date,
|
||||
delist_date=r.delist_date,
|
||||
industry=r.industry,
|
||||
status=r.status
|
||||
)
|
||||
symbols.append(s)
|
||||
|
||||
return symbols, total
|
||||
|
||||
def get_trading_dates(self, start: str, end: str) -> TradingDatesData:
|
||||
"""获取交易日历"""
|
||||
results = self.db.query(StockTradingCalendar).filter(
|
||||
StockTradingCalendar.trade_date >= start,
|
||||
StockTradingCalendar.trade_date <= end,
|
||||
StockTradingCalendar.is_trading_day == True
|
||||
).order_by(StockTradingCalendar.trade_date.asc()).all()
|
||||
|
||||
dates = [r.trade_date for r in results]
|
||||
|
||||
# 计算总天数
|
||||
start_date = datetime.strptime(start, "%Y%m%d")
|
||||
end_date = datetime.strptime(end, "%Y%m%d")
|
||||
total_days = (end_date - start_date).days + 1
|
||||
|
||||
return TradingDatesData(
|
||||
start=start,
|
||||
end=end,
|
||||
total_days=total_days,
|
||||
trading_days=len(dates),
|
||||
trading_dates=dates
|
||||
)
|
||||
|
||||
def save_symbols(self, symbols: List[Symbol]) -> None:
|
||||
"""保存标的列表"""
|
||||
for s in symbols:
|
||||
existing = self.db.query(StockSymbol).filter(
|
||||
StockSymbol.symbol_id == s.symbol_id
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.name = s.name
|
||||
existing.name_en = s.name_en
|
||||
existing.list_date = s.list_date
|
||||
existing.delist_date = s.delist_date
|
||||
existing.industry = s.industry
|
||||
existing.status = s.status
|
||||
else:
|
||||
new_symbol = StockSymbol(
|
||||
symbol_id=s.symbol_id,
|
||||
symbol_type=s.symbol_type.value if s.symbol_type else "stock",
|
||||
exchange=s.exchange.value if s.exchange else "",
|
||||
name=s.name,
|
||||
name_en=s.name_en,
|
||||
list_date=s.list_date,
|
||||
delist_date=s.delist_date,
|
||||
industry=s.industry,
|
||||
status=s.status
|
||||
)
|
||||
self.db.add(new_symbol)
|
||||
|
||||
self.db.commit()
|
||||
|
||||
def save_trading_calendar(self, dates: List[TradeCalData]) -> None:
|
||||
"""保存交易日历"""
|
||||
for d in dates:
|
||||
date_str = d.date.strftime("%Y%m%d")
|
||||
|
||||
existing = self.db.query(StockTradingCalendar).filter(
|
||||
StockTradingCalendar.trade_date == date_str
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.is_trading_day = d.is_trading_day
|
||||
existing.week_day = d.date.weekday() + 1
|
||||
else:
|
||||
new_cal = StockTradingCalendar(
|
||||
trade_date=date_str,
|
||||
is_trading_day=d.is_trading_day,
|
||||
week_day=d.date.weekday() + 1
|
||||
)
|
||||
self.db.add(new_cal)
|
||||
|
||||
self.db.commit()
|
||||
@ -1,16 +0,0 @@
|
||||
"""业务服务层模块"""
|
||||
from .stock_service import StockService
|
||||
from .futures_service import FuturesService
|
||||
from .admin_service import AdminService
|
||||
from .config_service import ConfigService
|
||||
from .adapter_service import AdapterService
|
||||
from .test_service import TestService
|
||||
|
||||
__all__ = [
|
||||
"StockService",
|
||||
"FuturesService",
|
||||
"AdminService",
|
||||
"ConfigService",
|
||||
"AdapterService",
|
||||
"TestService",
|
||||
]
|
||||
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.
@ -1,194 +0,0 @@
|
||||
"""适配器管理服务 - 对应Go的internal/service/adapter.go"""
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Callable
|
||||
from threading import RLock
|
||||
|
||||
from app.models import (
|
||||
AdapterListData, AdapterInfo, AdapterStatus,
|
||||
AdapterToggleRequest, AdapterConfigUpdateRequest
|
||||
)
|
||||
from app.adapters import DataSourceAdapter, TushareAdapter
|
||||
from app.core.logger import info, error
|
||||
|
||||
|
||||
class AdapterService:
|
||||
"""适配器管理服务"""
|
||||
|
||||
def __init__(self):
|
||||
self.lock = RLock()
|
||||
|
||||
# 已注册的适配器工厂
|
||||
self.factories: Dict[str, Callable[[], DataSourceAdapter]] = {}
|
||||
|
||||
# 适配器配置
|
||||
self.configs: Dict[str, dict] = {}
|
||||
|
||||
# 当前激活的适配器实例
|
||||
self.active_adapters: Dict[str, DataSourceAdapter] = {}
|
||||
|
||||
# 适配器元数据
|
||||
self.metadata: Dict[str, dict] = {}
|
||||
|
||||
# 注册内置适配器
|
||||
self._register_builtin_adapters()
|
||||
|
||||
def _register_builtin_adapters(self):
|
||||
"""注册内置适配器"""
|
||||
# 注册Tushare适配器
|
||||
self.register_adapter("tushare", lambda: TushareAdapter())
|
||||
|
||||
# 设置Tushare元数据
|
||||
self.metadata["tushare"] = {
|
||||
"name": "tushare",
|
||||
"type": "http",
|
||||
"version": "1.0.0",
|
||||
"description": "Tushare Pro 金融数据接口",
|
||||
"updated_at": datetime.now()
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
self.configs["tushare"] = {
|
||||
"enabled": True,
|
||||
"config": {
|
||||
"token": "",
|
||||
"base_url": "https://api.tushare.pro"
|
||||
}
|
||||
}
|
||||
|
||||
# 预留Wind适配器
|
||||
self.metadata["wind"] = {
|
||||
"name": "wind",
|
||||
"type": "ws",
|
||||
"version": "1.0.0",
|
||||
"description": "Wind 金融终端接口(预留)",
|
||||
"updated_at": datetime.now()
|
||||
}
|
||||
|
||||
self.configs["wind"] = {
|
||||
"enabled": False,
|
||||
"config": {
|
||||
"host": "localhost",
|
||||
"port": "8081"
|
||||
}
|
||||
}
|
||||
|
||||
def get_adapter_list(self) -> AdapterListData:
|
||||
"""获取适配器列表"""
|
||||
with self.lock:
|
||||
adapters = []
|
||||
|
||||
for name, meta in self.metadata.items():
|
||||
cfg = self.configs.get(name, {"enabled": False, "config": {}})
|
||||
|
||||
# 确定状态
|
||||
if not cfg["enabled"]:
|
||||
status = AdapterStatus.DISABLED
|
||||
elif name in self.active_adapters:
|
||||
status = AdapterStatus.ACTIVE
|
||||
else:
|
||||
status = AdapterStatus.STANDBY
|
||||
|
||||
adapters.append(AdapterInfo(
|
||||
name=meta["name"],
|
||||
type=meta["type"],
|
||||
version=meta["version"],
|
||||
description=meta["description"],
|
||||
status=status,
|
||||
config=cfg["config"],
|
||||
updated_at=meta["updated_at"]
|
||||
))
|
||||
|
||||
return AdapterListData(adapters=adapters)
|
||||
|
||||
def toggle_adapter(self, req: AdapterToggleRequest) -> None:
|
||||
"""启用/禁用适配器"""
|
||||
with self.lock:
|
||||
if req.name not in self.configs:
|
||||
raise ValueError(f"Adapter not found: {req.name}")
|
||||
|
||||
self.configs[req.name]["enabled"] = req.enable
|
||||
|
||||
# 如果禁用,关闭适配器连接
|
||||
if not req.enable and req.name in self.active_adapters:
|
||||
adapter = self.active_adapters.pop(req.name)
|
||||
asyncio.create_task(adapter.close())
|
||||
|
||||
# 更新元数据
|
||||
if req.name in self.metadata:
|
||||
self.metadata[req.name]["updated_at"] = datetime.now()
|
||||
|
||||
def update_adapter_config(self, req: AdapterConfigUpdateRequest) -> None:
|
||||
"""更新适配器配置"""
|
||||
with self.lock:
|
||||
if req.name not in self.configs:
|
||||
raise ValueError(f"Adapter not found: {req.name}")
|
||||
|
||||
# 更新配置
|
||||
self.configs[req.name]["config"].update(req.config)
|
||||
|
||||
# 如果适配器已激活,重新连接
|
||||
if req.name in self.active_adapters:
|
||||
adapter = self.active_adapters.pop(req.name)
|
||||
asyncio.create_task(adapter.close())
|
||||
|
||||
# 如果启用状态,重新连接
|
||||
if self.configs[req.name]["enabled"]:
|
||||
asyncio.create_task(self._connect_adapter(req.name))
|
||||
|
||||
# 更新元数据
|
||||
if req.name in self.metadata:
|
||||
self.metadata[req.name]["updated_at"] = datetime.now()
|
||||
|
||||
def get_active_adapter(self, asset_class: str) -> Optional[DataSourceAdapter]:
|
||||
"""获取当前激活的适配器"""
|
||||
with self.lock:
|
||||
# 根据资产类别获取配置(简化处理)
|
||||
adapter_name = "tushare"
|
||||
|
||||
# 检查是否已有激活的实例
|
||||
if adapter_name in self.active_adapters:
|
||||
return self.active_adapters[adapter_name]
|
||||
|
||||
return None
|
||||
|
||||
def get_available_adapters(self) -> List[str]:
|
||||
"""获取所有可用的适配器名称"""
|
||||
with self.lock:
|
||||
names = []
|
||||
for name, meta in self.metadata.items():
|
||||
if name in self.factories:
|
||||
names.append(f"{name}|{meta['description']}")
|
||||
return names
|
||||
|
||||
def register_adapter(self, name: str, factory: Callable[[], DataSourceAdapter]):
|
||||
"""注册适配器"""
|
||||
with self.lock:
|
||||
self.factories[name] = factory
|
||||
|
||||
async def _connect_adapter(self, name: str):
|
||||
"""连接适配器"""
|
||||
with self.lock:
|
||||
if name not in self.factories:
|
||||
raise ValueError(f"Adapter factory not found: {name}")
|
||||
|
||||
if name not in self.configs:
|
||||
raise ValueError(f"Adapter config not found: {name}")
|
||||
|
||||
factory = self.factories[name]
|
||||
cfg = self.configs[name]
|
||||
|
||||
adapter = factory()
|
||||
await adapter.connect(cfg["config"])
|
||||
|
||||
with self.lock:
|
||||
self.active_adapters[name] = adapter
|
||||
|
||||
async def health_check(self, name: str) -> bool:
|
||||
"""适配器健康检查"""
|
||||
with self.lock:
|
||||
if name not in self.active_adapters:
|
||||
return False
|
||||
adapter = self.active_adapters[name]
|
||||
|
||||
return await adapter.health_check()
|
||||
@ -1,332 +0,0 @@
|
||||
"""配置管理服务 - 对应Go的internal/service/config.go"""
|
||||
import platform
|
||||
import psutil
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, List, Callable, Dict, Any
|
||||
|
||||
from app.models import (
|
||||
ConfigListRequest, ConfigListData, ConfigSection, ConfigItem,
|
||||
ConfigUpdateRequest, ConfigUpdateData, ConfigType,
|
||||
ReloadRequest, ReloadData, SystemStatusData, MemoryInfo
|
||||
)
|
||||
from app.core.config import get_config, reload_config, save_config, Config
|
||||
from app.core.logger import info
|
||||
|
||||
|
||||
class ConfigService:
|
||||
"""配置管理服务"""
|
||||
|
||||
def __init__(self):
|
||||
self.config = get_config()
|
||||
self.start_time = datetime.now()
|
||||
self.version = "1.0.0"
|
||||
self.callbacks: Dict[ConfigType, List[Callable]] = {}
|
||||
self.lock = threading.RLock()
|
||||
|
||||
def get_config_list(self, req: ConfigListRequest) -> ConfigListData:
|
||||
"""获取配置列表"""
|
||||
sections = []
|
||||
|
||||
# 服务器配置
|
||||
if not req.type or req.type == ConfigType.SERVER:
|
||||
sections.append(ConfigSection(
|
||||
name="服务器配置",
|
||||
type=ConfigType.SERVER,
|
||||
description="HTTP服务器相关配置",
|
||||
items=[
|
||||
ConfigItem(
|
||||
key="port",
|
||||
value=self.config.server.port,
|
||||
type="int",
|
||||
description="服务端口",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="mode",
|
||||
value=self.config.server.mode,
|
||||
type="string",
|
||||
description="运行模式: debug/release",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="api_key",
|
||||
value=self.config.server.api_key,
|
||||
type="string",
|
||||
description="API认证密钥",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
]
|
||||
))
|
||||
|
||||
# 数据库配置
|
||||
if not req.type or req.type == ConfigType.DATABASE:
|
||||
sections.append(ConfigSection(
|
||||
name="数据库配置",
|
||||
type=ConfigType.DATABASE,
|
||||
description="PostgreSQL数据库连接配置",
|
||||
items=[
|
||||
ConfigItem(
|
||||
key="host",
|
||||
value=self.config.database.host,
|
||||
type="string",
|
||||
description="数据库主机地址",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="port",
|
||||
value=self.config.database.port,
|
||||
type="int",
|
||||
description="数据库端口",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="user",
|
||||
value=self.config.database.user,
|
||||
type="string",
|
||||
description="数据库用户名",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="password",
|
||||
value="********",
|
||||
type="password",
|
||||
description="数据库密码",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="database",
|
||||
value=self.config.database.database,
|
||||
type="string",
|
||||
description="数据库名",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
]
|
||||
))
|
||||
|
||||
# Redis配置
|
||||
if not req.type or req.type == ConfigType.REDIS:
|
||||
sections.append(ConfigSection(
|
||||
name="Redis配置",
|
||||
type=ConfigType.REDIS,
|
||||
description="Redis缓存配置",
|
||||
items=[
|
||||
ConfigItem(
|
||||
key="host",
|
||||
value=self.config.redis.host,
|
||||
type="string",
|
||||
description="Redis主机地址",
|
||||
editable=True,
|
||||
required=False
|
||||
),
|
||||
ConfigItem(
|
||||
key="port",
|
||||
value=self.config.redis.port,
|
||||
type="int",
|
||||
description="Redis端口",
|
||||
editable=True,
|
||||
required=False
|
||||
),
|
||||
ConfigItem(
|
||||
key="password",
|
||||
value="********",
|
||||
type="password",
|
||||
description="Redis密码",
|
||||
editable=True,
|
||||
required=False
|
||||
),
|
||||
ConfigItem(
|
||||
key="db",
|
||||
value=self.config.redis.db,
|
||||
type="int",
|
||||
description="Redis数据库编号",
|
||||
editable=True,
|
||||
required=False
|
||||
),
|
||||
]
|
||||
))
|
||||
|
||||
# 数据源配置
|
||||
if not req.type or req.type == ConfigType.SOURCE:
|
||||
sections.append(ConfigSection(
|
||||
name="数据源配置",
|
||||
type=ConfigType.SOURCE,
|
||||
description="股票和期货数据源配置",
|
||||
items=[
|
||||
ConfigItem(
|
||||
key="stock_active",
|
||||
value=self.config.sources.stock.active,
|
||||
type="string",
|
||||
description="股票数据源适配器",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
ConfigItem(
|
||||
key="futures_active",
|
||||
value=self.config.sources.futures.active,
|
||||
type="string",
|
||||
description="期货数据源适配器",
|
||||
editable=True,
|
||||
required=True
|
||||
),
|
||||
]
|
||||
))
|
||||
|
||||
return ConfigListData(
|
||||
sections=sections,
|
||||
version=self.version,
|
||||
updated=datetime.now()
|
||||
)
|
||||
|
||||
def update_config(self, req: ConfigUpdateRequest) -> ConfigUpdateData:
|
||||
"""更新配置"""
|
||||
need_restart = False
|
||||
|
||||
with self.lock:
|
||||
if req.type == ConfigType.SERVER:
|
||||
if "port" in req.items:
|
||||
self.config.server.port = int(req.items["port"])
|
||||
need_restart = True
|
||||
if "mode" in req.items:
|
||||
self.config.server.mode = req.items["mode"]
|
||||
if "api_key" in req.items:
|
||||
self.config.server.api_key = req.items["api_key"]
|
||||
|
||||
elif req.type == ConfigType.DATABASE:
|
||||
if "host" in req.items:
|
||||
self.config.database.host = req.items["host"]
|
||||
need_restart = True
|
||||
if "port" in req.items:
|
||||
self.config.database.port = int(req.items["port"])
|
||||
need_restart = True
|
||||
if "user" in req.items:
|
||||
self.config.database.user = req.items["user"]
|
||||
need_restart = True
|
||||
if "password" in req.items:
|
||||
password = req.items["password"]
|
||||
if password != "********":
|
||||
self.config.database.password = password
|
||||
need_restart = True
|
||||
if "database" in req.items:
|
||||
self.config.database.database = req.items["database"]
|
||||
need_restart = True
|
||||
|
||||
elif req.type == ConfigType.SOURCE:
|
||||
if "stock_active" in req.items:
|
||||
self.config.sources.stock.active = req.items["stock_active"]
|
||||
if "futures_active" in req.items:
|
||||
self.config.sources.futures.active = req.items["futures_active"]
|
||||
|
||||
# 保存到文件
|
||||
try:
|
||||
save_config(self.config)
|
||||
self._trigger_callbacks(req.type)
|
||||
|
||||
message = "配置更新成功"
|
||||
if need_restart:
|
||||
message += ",部分配置需要重启服务后生效"
|
||||
|
||||
return ConfigUpdateData(
|
||||
success=True,
|
||||
need_restart=need_restart,
|
||||
message=message
|
||||
)
|
||||
except Exception as e:
|
||||
return ConfigUpdateData(
|
||||
success=False,
|
||||
need_restart=False,
|
||||
message=f"配置保存失败: {e}"
|
||||
)
|
||||
|
||||
def reload_config(self, req: ReloadRequest) -> ReloadData:
|
||||
"""热加载配置"""
|
||||
try:
|
||||
with self.lock:
|
||||
new_config = reload_config()
|
||||
|
||||
# 根据类型选择性更新
|
||||
if req.config_type is None:
|
||||
self.config = new_config
|
||||
else:
|
||||
if req.config_type == ConfigType.SERVER:
|
||||
self.config.server = new_config.server
|
||||
elif req.config_type == ConfigType.DATABASE:
|
||||
self.config.database = new_config.database
|
||||
elif req.config_type == ConfigType.REDIS:
|
||||
self.config.redis = new_config.redis
|
||||
elif req.config_type == ConfigType.SOURCE:
|
||||
self.config.sources = new_config.sources
|
||||
|
||||
self._trigger_callbacks(req.config_type)
|
||||
|
||||
return ReloadData(
|
||||
success=True,
|
||||
message="配置热加载成功"
|
||||
)
|
||||
except Exception as e:
|
||||
return ReloadData(
|
||||
success=False,
|
||||
message=f"加载配置失败: {e}"
|
||||
)
|
||||
|
||||
def get_system_status(self) -> SystemStatusData:
|
||||
"""获取系统状态"""
|
||||
# 获取内存信息
|
||||
mem = psutil.virtual_memory()
|
||||
|
||||
# 计算运行时长
|
||||
uptime = datetime.now() - self.start_time
|
||||
uptime_str = self._format_duration(uptime)
|
||||
|
||||
return SystemStatusData(
|
||||
status="running",
|
||||
version=self.version,
|
||||
start_time=self.start_time,
|
||||
uptime=uptime_str,
|
||||
python_version=platform.python_version(),
|
||||
memory=MemoryInfo(
|
||||
alloc=mem.used,
|
||||
total_alloc=mem.total,
|
||||
sys=mem.total,
|
||||
num_gc=0 # Python不需要显式GC计数
|
||||
),
|
||||
threads=threading.active_count()
|
||||
)
|
||||
|
||||
def _format_duration(self, d: timedelta) -> str:
|
||||
"""格式化持续时间"""
|
||||
days = d.days
|
||||
hours, remainder = divmod(d.seconds, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
|
||||
if days > 0:
|
||||
return f"{days}天{hours}小时{minutes}分钟"
|
||||
if hours > 0:
|
||||
return f"{hours}小时{minutes}分钟"
|
||||
return f"{minutes}分钟"
|
||||
|
||||
def register_callback(self, config_type: ConfigType, callback: Callable):
|
||||
"""注册配置变更回调"""
|
||||
with self.lock:
|
||||
if config_type not in self.callbacks:
|
||||
self.callbacks[config_type] = []
|
||||
self.callbacks[config_type].append(callback)
|
||||
|
||||
def _trigger_callbacks(self, config_type: Optional[ConfigType]):
|
||||
"""触发回调"""
|
||||
with self.lock:
|
||||
# 触发特定类型的回调
|
||||
if config_type and config_type in self.callbacks:
|
||||
for cb in self.callbacks[config_type]:
|
||||
try:
|
||||
cb()
|
||||
except Exception as e:
|
||||
info(f"Callback error: {e}")
|
||||
@ -1,102 +0,0 @@
|
||||
"""期货业务服务 - 对应Go的internal/service/futures.go"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import (
|
||||
KLineQueryRequest, KLineData, SymbolListRequest, SymbolListData,
|
||||
BatchKLineRequest, BatchKLineData, BatchKLineResult, KLineSubData,
|
||||
TradingDatesRequest, TradingDatesData,
|
||||
FuturesContractsRequest, FuturesContractsData
|
||||
)
|
||||
from app.repositories import FuturesRepository
|
||||
from app.core.logger import error
|
||||
|
||||
|
||||
class FuturesService:
|
||||
"""期货业务服务"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.repository = FuturesRepository(db)
|
||||
|
||||
def query_klines(self, req: KLineQueryRequest) -> KLineData:
|
||||
"""查询K线数据"""
|
||||
# 解析日期
|
||||
try:
|
||||
start = datetime.strptime(req.start, "%Y%m%d")
|
||||
end = datetime.strptime(req.end, "%Y%m%d")
|
||||
end = end + timedelta(days=1) - timedelta(seconds=1)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid date format: {e}")
|
||||
|
||||
# 获取K线数据
|
||||
items = self.repository.get_klines(req.symbol, req.freq, start, end)
|
||||
|
||||
return KLineData(
|
||||
symbol=req.symbol,
|
||||
freq=req.freq,
|
||||
count=len(items),
|
||||
items=items
|
||||
)
|
||||
|
||||
def list_symbols(self, req: SymbolListRequest) -> SymbolListData:
|
||||
"""查询标的列表"""
|
||||
if req.page <= 0:
|
||||
req.page = 1
|
||||
if req.size <= 0:
|
||||
req.size = 20
|
||||
if req.size > 100:
|
||||
req.size = 100
|
||||
|
||||
symbols, total = self.repository.list_symbols(req)
|
||||
|
||||
return SymbolListData(
|
||||
total=total,
|
||||
page=req.page,
|
||||
size=req.size,
|
||||
items=symbols
|
||||
)
|
||||
|
||||
def batch_query_klines(self, req: BatchKLineRequest) -> BatchKLineData:
|
||||
"""批量查询K线"""
|
||||
results = []
|
||||
|
||||
for symbol in req.symbols:
|
||||
single_req = KLineQueryRequest(
|
||||
symbol=symbol,
|
||||
start=req.start,
|
||||
end=req.end,
|
||||
freq=req.freq
|
||||
)
|
||||
|
||||
try:
|
||||
data = self.query_klines(single_req)
|
||||
results.append(BatchKLineResult(
|
||||
symbol=symbol,
|
||||
success=True,
|
||||
data=KLineSubData(count=data.count, items=data.items)
|
||||
))
|
||||
except Exception as e:
|
||||
error(f"Batch query failed for {symbol}: {e}")
|
||||
results.append(BatchKLineResult(
|
||||
symbol=symbol,
|
||||
success=False,
|
||||
error=str(e)
|
||||
))
|
||||
|
||||
return BatchKLineData(results=results)
|
||||
|
||||
def get_trading_dates(self, req: TradingDatesRequest) -> TradingDatesData:
|
||||
"""获取交易日历"""
|
||||
return self.repository.get_trading_dates(req.start, req.end)
|
||||
|
||||
def get_contracts_by_underlying(
|
||||
self,
|
||||
req: FuturesContractsRequest
|
||||
) -> FuturesContractsData:
|
||||
"""根据品种获取合约"""
|
||||
return self.repository.get_contracts_by_underlying(
|
||||
req.underlying,
|
||||
req.exchange
|
||||
)
|
||||
@ -1,4 +0,0 @@
|
||||
"""WebSocket服务模块"""
|
||||
from .server import WebSocketServer, ws_manager
|
||||
|
||||
__all__ = ["WebSocketServer", "ws_manager"]
|
||||
Binary file not shown.
Binary file not shown.
@ -1,210 +0,0 @@
|
||||
"""WebSocket服务 - 对应Go的internal/websocket/server.go"""
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, Set, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
|
||||
from app.core.logger import info, error
|
||||
|
||||
|
||||
@dataclass
|
||||
class WSClient:
|
||||
"""WebSocket客户端"""
|
||||
id: str
|
||||
websocket: WebSocket
|
||||
subscriptions: Set[str] = field(default_factory=set)
|
||||
|
||||
async def send(self, message: dict):
|
||||
"""发送消息"""
|
||||
try:
|
||||
await self.websocket.send_json(message)
|
||||
except Exception as e:
|
||||
error(f"Failed to send message to client {self.id}: {e}")
|
||||
|
||||
|
||||
class WebSocketManager:
|
||||
"""WebSocket连接管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.clients: Dict[str, WSClient] = {}
|
||||
self.subscriptions: Dict[str, Set[str]] = {} # symbol -> set of client_ids
|
||||
self.max_symbols_per_client = 100
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def connect(self, websocket: WebSocket, client_id: str) -> WSClient:
|
||||
"""建立连接"""
|
||||
await websocket.accept()
|
||||
|
||||
client = WSClient(id=client_id, websocket=websocket)
|
||||
|
||||
async with self.lock:
|
||||
self.clients[client_id] = client
|
||||
|
||||
info(f"WebSocket client connected: {client_id}, total: {len(self.clients)}")
|
||||
return client
|
||||
|
||||
async def disconnect(self, client_id: str):
|
||||
"""断开连接"""
|
||||
async with self.lock:
|
||||
if client_id in self.clients:
|
||||
client = self.clients.pop(client_id)
|
||||
|
||||
# 清理订阅
|
||||
for symbol in client.subscriptions:
|
||||
if symbol in self.subscriptions:
|
||||
self.subscriptions[symbol].discard(client_id)
|
||||
if not self.subscriptions[symbol]:
|
||||
del self.subscriptions[symbol]
|
||||
|
||||
info(f"WebSocket client disconnected: {client_id}, total: {len(self.clients)}")
|
||||
|
||||
async def subscribe(self, client_id: str, symbols: list) -> bool:
|
||||
"""订阅标的"""
|
||||
async with self.lock:
|
||||
if client_id not in self.clients:
|
||||
return False
|
||||
|
||||
client = self.clients[client_id]
|
||||
|
||||
# 检查订阅数量限制
|
||||
if len(client.subscriptions) + len(symbols) > self.max_symbols_per_client:
|
||||
return False
|
||||
|
||||
for symbol in symbols:
|
||||
client.subscriptions.add(symbol)
|
||||
|
||||
if symbol not in self.subscriptions:
|
||||
self.subscriptions[symbol] = set()
|
||||
self.subscriptions[symbol].add(client_id)
|
||||
|
||||
return True
|
||||
|
||||
async def unsubscribe(self, client_id: str, symbols: list):
|
||||
"""取消订阅"""
|
||||
async with self.lock:
|
||||
if client_id not in self.clients:
|
||||
return
|
||||
|
||||
client = self.clients[client_id]
|
||||
|
||||
for symbol in symbols:
|
||||
client.subscriptions.discard(symbol)
|
||||
|
||||
if symbol in self.subscriptions:
|
||||
self.subscriptions[symbol].discard(client_id)
|
||||
if not self.subscriptions[symbol]:
|
||||
del self.subscriptions[symbol]
|
||||
|
||||
async def broadcast_to_symbol(self, symbol: str, message: dict):
|
||||
"""向订阅了某标的的所有客户端广播"""
|
||||
client_ids = set()
|
||||
|
||||
async with self.lock:
|
||||
if symbol in self.subscriptions:
|
||||
client_ids = self.subscriptions[symbol].copy()
|
||||
|
||||
# 在锁外发送消息
|
||||
for client_id in client_ids:
|
||||
if client_id in self.clients:
|
||||
try:
|
||||
await self.clients[client_id].send(message)
|
||||
except Exception as e:
|
||||
error(f"Failed to broadcast to {client_id}: {e}")
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""获取统计信息"""
|
||||
return {
|
||||
"total_clients": len(self.clients),
|
||||
"total_subscriptions": len(self.subscriptions)
|
||||
}
|
||||
|
||||
|
||||
# 全局WebSocket管理器实例
|
||||
ws_manager = WebSocketManager()
|
||||
|
||||
|
||||
class WebSocketServer:
|
||||
"""WebSocket服务器"""
|
||||
|
||||
def __init__(self):
|
||||
self.manager = ws_manager
|
||||
|
||||
async def handle(self, websocket: WebSocket, client_id: str):
|
||||
"""处理WebSocket连接"""
|
||||
client = await self.manager.connect(websocket, client_id)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# 接收消息
|
||||
data = await websocket.receive_text()
|
||||
|
||||
try:
|
||||
msg = json.loads(data)
|
||||
action = msg.get("action")
|
||||
symbols = msg.get("symbols", [])
|
||||
|
||||
if action == "subscribe":
|
||||
success = await self.manager.subscribe(client_id, symbols)
|
||||
if success:
|
||||
await client.send({
|
||||
"type": "ack",
|
||||
"action": "subscribe",
|
||||
"symbols": symbols,
|
||||
"ts": datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
await client.send({
|
||||
"type": "error",
|
||||
"code": 1003,
|
||||
"message": "Too many subscriptions or subscription failed",
|
||||
"ts": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
elif action == "unsubscribe":
|
||||
await self.manager.unsubscribe(client_id, symbols)
|
||||
await client.send({
|
||||
"type": "ack",
|
||||
"action": "unsubscribe",
|
||||
"symbols": symbols,
|
||||
"ts": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
else:
|
||||
await client.send({
|
||||
"type": "error",
|
||||
"code": 1001,
|
||||
"message": "Unknown action",
|
||||
"ts": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
await client.send({
|
||||
"type": "error",
|
||||
"code": 1000,
|
||||
"message": "Invalid message format",
|
||||
"ts": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except WebSocketDisconnect:
|
||||
await self.manager.disconnect(client_id)
|
||||
except Exception as e:
|
||||
error(f"WebSocket error for client {client_id}: {e}")
|
||||
await self.manager.disconnect(client_id)
|
||||
|
||||
async def send_heartbeat(self):
|
||||
"""发送心跳(可由定时任务调用)"""
|
||||
message = {
|
||||
"type": "heartbeat",
|
||||
"ts": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# 向所有客户端发送心跳
|
||||
clients_copy = list(self.manager.clients.values())
|
||||
for client in clients_copy:
|
||||
try:
|
||||
await client.send(message)
|
||||
except Exception:
|
||||
pass
|
||||
@ -1,46 +0,0 @@
|
||||
{
|
||||
"server": {
|
||||
"port": 8080,
|
||||
"mode": "debug",
|
||||
"api_key": "demo-api-key-2024"
|
||||
},
|
||||
"database": {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"database": "marketdata"
|
||||
},
|
||||
"redis": {
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"password": "",
|
||||
"db": 0
|
||||
},
|
||||
"sources": {
|
||||
"stock": {
|
||||
"active": "tushare",
|
||||
"list": {
|
||||
"tushare": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"token": "",
|
||||
"base_url": "https://api.tushare.pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"futures": {
|
||||
"active": "tushare",
|
||||
"list": {
|
||||
"tushare": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"token": "",
|
||||
"base_url": "https://api.tushare.pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "market-data-service"
|
||||
version = "1.0.0"
|
||||
description = "统一行情数据服务 - Python实现"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi>=0.115.0",
|
||||
"uvicorn[standard]>=0.32.0",
|
||||
"python-socketio>=5.12.1",
|
||||
"websockets>=14.1",
|
||||
"sqlalchemy>=2.0.36",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"pandas>=2.2.3",
|
||||
"numpy>=2.1.3",
|
||||
"pydantic>=2.10.0",
|
||||
"pydantic-settings>=2.6.1",
|
||||
"python-dotenv>=1.0.1",
|
||||
"PyYAML>=6.0.2",
|
||||
"httpx>=0.28.0",
|
||||
"apscheduler>=3.11.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.3.4",
|
||||
"pytest-asyncio>=0.24.0",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["app*"]
|
||||
@ -1,38 +0,0 @@
|
||||
# Web Framework
|
||||
fastapi==0.115.0
|
||||
uvicorn[standard]==0.32.0
|
||||
python-socketio==5.12.1
|
||||
websockets==14.1
|
||||
|
||||
# Database
|
||||
sqlalchemy==2.0.36
|
||||
psycopg2-binary==2.9.10
|
||||
alembic==1.14.0
|
||||
|
||||
# Data Processing
|
||||
pandas==2.2.3
|
||||
numpy==2.1.3
|
||||
|
||||
# Data Source
|
||||
# Note: tushare needs to be installed separately with: pip install tushare
|
||||
tushare==1.4.14
|
||||
|
||||
# Configuration
|
||||
pydantic==2.10.0
|
||||
pydantic-settings==2.6.1
|
||||
python-dotenv==1.0.1
|
||||
PyYAML==6.0.2
|
||||
|
||||
# Utilities
|
||||
python-multipart==0.0.19
|
||||
httpx==0.28.0
|
||||
aiohttp==3.11.10
|
||||
aioredis==2.0.1
|
||||
|
||||
# Monitoring
|
||||
apscheduler==3.11.0
|
||||
|
||||
# Testing
|
||||
pytest==8.3.4
|
||||
pytest-asyncio==0.24.0
|
||||
httpx==0.28.0
|
||||
@ -1,164 +0,0 @@
|
||||
/* -*- 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.
@ -1 +0,0 @@
|
||||
pip
|
||||
@ -1,19 +0,0 @@
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Copyright (c) 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.
|
||||
@ -1,147 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: APScheduler
|
||||
Version: 3.11.0
|
||||
Summary: In-process task scheduler with Cron-like capabilities
|
||||
Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://apscheduler.readthedocs.io/en/3.x/
|
||||
Project-URL: Changelog, https://apscheduler.readthedocs.io/en/3.x/versionhistory.html
|
||||
Project-URL: Source code, https://github.com/agronholm/apscheduler
|
||||
Project-URL: Issue tracker, https://github.com/agronholm/apscheduler/issues
|
||||
Keywords: scheduling,cron
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
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
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.txt
|
||||
Requires-Dist: tzlocal>=3.0
|
||||
Requires-Dist: backports.zoneinfo; python_version < "3.9"
|
||||
Provides-Extra: etcd
|
||||
Requires-Dist: etcd3; extra == "etcd"
|
||||
Requires-Dist: protobuf<=3.21.0; extra == "etcd"
|
||||
Provides-Extra: gevent
|
||||
Requires-Dist: gevent; extra == "gevent"
|
||||
Provides-Extra: mongodb
|
||||
Requires-Dist: pymongo>=3.0; extra == "mongodb"
|
||||
Provides-Extra: redis
|
||||
Requires-Dist: redis>=3.0; extra == "redis"
|
||||
Provides-Extra: rethinkdb
|
||||
Requires-Dist: rethinkdb>=2.4.0; extra == "rethinkdb"
|
||||
Provides-Extra: sqlalchemy
|
||||
Requires-Dist: sqlalchemy>=1.4; extra == "sqlalchemy"
|
||||
Provides-Extra: tornado
|
||||
Requires-Dist: tornado>=4.3; extra == "tornado"
|
||||
Provides-Extra: twisted
|
||||
Requires-Dist: twisted; extra == "twisted"
|
||||
Provides-Extra: zookeeper
|
||||
Requires-Dist: kazoo; extra == "zookeeper"
|
||||
Provides-Extra: test
|
||||
Requires-Dist: APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]; extra == "test"
|
||||
Requires-Dist: pytest; extra == "test"
|
||||
Requires-Dist: anyio>=4.5.2; extra == "test"
|
||||
Requires-Dist: PySide6; (platform_python_implementation == "CPython" and python_version < "3.14") and extra == "test"
|
||||
Requires-Dist: gevent; python_version < "3.14" and extra == "test"
|
||||
Requires-Dist: pytz; extra == "test"
|
||||
Requires-Dist: twisted; python_version < "3.14" and extra == "test"
|
||||
Provides-Extra: doc
|
||||
Requires-Dist: packaging; extra == "doc"
|
||||
Requires-Dist: sphinx; extra == "doc"
|
||||
Requires-Dist: sphinx-rtd-theme>=1.3.0; extra == "doc"
|
||||
|
||||
.. image:: https://github.com/agronholm/apscheduler/workflows/Python%20codeqa/test/badge.svg?branch=3.x
|
||||
:target: https://github.com/agronholm/apscheduler/actions?query=workflow%3A%22Python+codeqa%2Ftest%22+branch%3A3.x
|
||||
:alt: Build Status
|
||||
.. image:: https://coveralls.io/repos/github/agronholm/apscheduler/badge.svg?branch=3.x
|
||||
:target: https://coveralls.io/github/agronholm/apscheduler?branch=3.x
|
||||
:alt: Code Coverage
|
||||
.. image:: https://readthedocs.org/projects/apscheduler/badge/?version=3.x
|
||||
:target: https://apscheduler.readthedocs.io/en/master/?badge=3.x
|
||||
:alt: Documentation
|
||||
|
||||
Advanced Python Scheduler (APScheduler) is a Python library that lets you schedule your Python code
|
||||
to be executed later, either just once or periodically. You can add new jobs or remove old ones on
|
||||
the fly as you please. If you store your jobs in a database, they will also survive scheduler
|
||||
restarts and maintain their state. When the scheduler is restarted, it will then run all the jobs
|
||||
it should have run while it was offline [#f1]_.
|
||||
|
||||
Among other things, APScheduler can be used as a cross-platform, application specific replacement
|
||||
to platform specific schedulers, such as the cron daemon or the Windows task scheduler. Please
|
||||
note, however, that APScheduler is **not** a daemon or service itself, nor does it come with any
|
||||
command line tools. It is primarily meant to be run inside existing applications. That said,
|
||||
APScheduler does provide some building blocks for you to build a scheduler service or to run a
|
||||
dedicated scheduler process.
|
||||
|
||||
APScheduler has three built-in scheduling systems you can use:
|
||||
|
||||
* Cron-style scheduling (with optional start/end times)
|
||||
* Interval-based execution (runs jobs on even intervals, with optional start/end times)
|
||||
* One-off delayed execution (runs jobs once, on a set date/time)
|
||||
|
||||
You can mix and match scheduling systems and the backends where the jobs are stored any way you
|
||||
like. Supported backends for storing jobs include:
|
||||
|
||||
* Memory
|
||||
* `SQLAlchemy <http://www.sqlalchemy.org/>`_ (any RDBMS supported by SQLAlchemy works)
|
||||
* `MongoDB <http://www.mongodb.org/>`_
|
||||
* `Redis <http://redis.io/>`_
|
||||
* `RethinkDB <https://www.rethinkdb.com/>`_
|
||||
* `ZooKeeper <https://zookeeper.apache.org/>`_
|
||||
* `Etcd <https://etcd.io/>`_
|
||||
|
||||
APScheduler also integrates with several common Python frameworks, like:
|
||||
|
||||
* `asyncio <http://docs.python.org/3.4/library/asyncio.html>`_ (:pep:`3156`)
|
||||
* `gevent <http://www.gevent.org/>`_
|
||||
* `Tornado <http://www.tornadoweb.org/>`_
|
||||
* `Twisted <http://twistedmatrix.com/>`_
|
||||
* `Qt <http://qt-project.org/>`_ (using either
|
||||
`PyQt <http://www.riverbankcomputing.com/software/pyqt/intro>`_ ,
|
||||
`PySide6 <https://wiki.qt.io/Qt_for_Python>`_ ,
|
||||
`PySide2 <https://wiki.qt.io/Qt_for_Python>`_ or
|
||||
`PySide <http://qt-project.org/wiki/PySide>`_)
|
||||
|
||||
There are third party solutions for integrating APScheduler with other frameworks:
|
||||
|
||||
* `Django <https://github.com/jarekwg/django-apscheduler>`_
|
||||
* `Flask <https://github.com/viniciuschiele/flask-apscheduler>`_
|
||||
|
||||
|
||||
.. [#f1] The cutoff period for this is also configurable.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation can be found `here <https://apscheduler.readthedocs.io/>`_.
|
||||
|
||||
|
||||
Source
|
||||
------
|
||||
|
||||
The source can be browsed at `Github <https://github.com/agronholm/apscheduler/tree/3.x>`_.
|
||||
|
||||
|
||||
Reporting bugs
|
||||
--------------
|
||||
|
||||
A `bug tracker <https://github.com/agronholm/apscheduler/issues>`_ is provided by Github.
|
||||
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
If you have problems or other questions, you can either:
|
||||
|
||||
* Ask in the `apscheduler <https://gitter.im/apscheduler/Lobby>`_ room on Gitter
|
||||
* Ask on the `APScheduler GitHub discussion forum <https://github.com/agronholm/apscheduler/discussions>`_, or
|
||||
* Ask on `StackOverflow <http://stackoverflow.com/questions/tagged/apscheduler>`_ and tag your
|
||||
question with the ``apscheduler`` tag
|
||||
@ -1,86 +0,0 @@
|
||||
APScheduler-3.11.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
APScheduler-3.11.0.dist-info/LICENSE.txt,sha256=YWP3mH37ONa8MgzitwsvArhivEESZRbVUu8c1DJH51g,1130
|
||||
APScheduler-3.11.0.dist-info/METADATA,sha256=Mve2P3vZbWWDb5V-XfZO80hkih9E6s00Nn5ptU2__9w,6374
|
||||
APScheduler-3.11.0.dist-info/RECORD,,
|
||||
APScheduler-3.11.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
APScheduler-3.11.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
||||
APScheduler-3.11.0.dist-info/entry_points.txt,sha256=HSDTxgulLTgymfXK2UNCPP1ib5rlQSFgZJEg72vto3s,1181
|
||||
APScheduler-3.11.0.dist-info/top_level.txt,sha256=O3oMCWxG-AHkecUoO6Ze7-yYjWrttL95uHO8-RFdYvE,12
|
||||
apscheduler/__init__.py,sha256=hOpI9oJuk5l5I_VtdsHPous2Qr-ZDX573e7NaYRWFUs,380
|
||||
apscheduler/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/__pycache__/events.cpython-311.pyc,,
|
||||
apscheduler/__pycache__/job.cpython-311.pyc,,
|
||||
apscheduler/__pycache__/util.cpython-311.pyc,,
|
||||
apscheduler/events.py,sha256=W_Wg5aTBXDxXhHtimn93ZEjV3x0ntF-Y0EAVuZPhiXY,3591
|
||||
apscheduler/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
apscheduler/executors/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/asyncio.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/base.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/debug.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/gevent.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/pool.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/tornado.cpython-311.pyc,,
|
||||
apscheduler/executors/__pycache__/twisted.cpython-311.pyc,,
|
||||
apscheduler/executors/asyncio.py,sha256=g0ArcxefoTnEqtyr_IRc-M3dcj0bhuvHcxwRp2s3nDE,1768
|
||||
apscheduler/executors/base.py,sha256=HErgd8d1g0-BjXnylLcFyoo6GU3wHgW9GJVaFNMV7dI,7116
|
||||
apscheduler/executors/debug.py,sha256=15_ogSBzl8RRCfBYDnkIV2uMH8cLk1KImYmBa_NVGpc,573
|
||||
apscheduler/executors/gevent.py,sha256=_ZFpbn7-tH5_lAeL4sxEyPhxyUTtUUSrH8s42EHGQ2w,761
|
||||
apscheduler/executors/pool.py,sha256=q_shxnvXLjdcwhtKyPvQSYngOjAeKQO8KCvZeb19RSQ,2683
|
||||
apscheduler/executors/tornado.py,sha256=lb6mshRj7GMLz3d8StwESnlZsAfrNmW78Wokcg__Lk8,1581
|
||||
apscheduler/executors/twisted.py,sha256=YUEDnaPbP_M0lXCmNAW_yPiLKwbO9vD3KMiBFQ2D4h0,726
|
||||
apscheduler/job.py,sha256=GzOGMfOM6STwd3HWArVAylO-1Kb0f2qA_PRuXs5LPk4,11153
|
||||
apscheduler/jobstores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
apscheduler/jobstores/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/base.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/etcd.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/memory.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/mongodb.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/redis.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/rethinkdb.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/sqlalchemy.cpython-311.pyc,,
|
||||
apscheduler/jobstores/__pycache__/zookeeper.cpython-311.pyc,,
|
||||
apscheduler/jobstores/base.py,sha256=ZDOgMtHLaF3TPUOQwmkBIDcpnHU0aUhtzZOGmMGaJn8,4416
|
||||
apscheduler/jobstores/etcd.py,sha256=O7C40CGlnn3cPinchJEs2sWcqnzEZQt3c6WnhgPRSdQ,5703
|
||||
apscheduler/jobstores/memory.py,sha256=HmOs7FbrOoQNywz-yfq2v5esGDHeKE_mvMNFDeGZ31E,3595
|
||||
apscheduler/jobstores/mongodb.py,sha256=mCIwcKiWcicM2qdAQn51QBEkGlNfbk_73Oi6soShNcM,5319
|
||||
apscheduler/jobstores/redis.py,sha256=El-H2eUfZjPZca7vwy10B9gZv5RzRucbkDu7Ti07vyM,5482
|
||||
apscheduler/jobstores/rethinkdb.py,sha256=SdT3jPrhxnmBoL4IClDfHsez4DpREnYEsHndIP8idHA,5922
|
||||
apscheduler/jobstores/sqlalchemy.py,sha256=2jaq3ZcoXEyIqqvYf3eloaP-_ZAqojt0EuWWvQ2LMRg,6799
|
||||
apscheduler/jobstores/zookeeper.py,sha256=32bEZNJNniPwmYXBITZ3eSRBq6hipqPKDqh4q4NiZvc,6439
|
||||
apscheduler/schedulers/__init__.py,sha256=POEy7n3BZgccZ44atMvxj0w5PejN55g-55NduZUZFqQ,406
|
||||
apscheduler/schedulers/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/asyncio.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/background.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/base.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/blocking.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/gevent.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/qt.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/tornado.cpython-311.pyc,,
|
||||
apscheduler/schedulers/__pycache__/twisted.cpython-311.pyc,,
|
||||
apscheduler/schedulers/asyncio.py,sha256=Jo7tgHP1STnMSxNVAWPSkFpmBLngavivTsG9sF0QoWM,1893
|
||||
apscheduler/schedulers/background.py,sha256=sRNrikUhpyblvA5RCpKC5Djvf3-b6NHvnXTblxlqIaM,1476
|
||||
apscheduler/schedulers/base.py,sha256=hvnvcI1DOC9bmvrFk8UiLlGxsXKHtMpEHLDEe63mQ_s,48342
|
||||
apscheduler/schedulers/blocking.py,sha256=138rf9X1C-ZxWVTVAO_pyfYMBKhkqO2qZqJoyGInv5c,872
|
||||
apscheduler/schedulers/gevent.py,sha256=zS5nHQUkQMrn0zKOaFnUyiG0fXTE01yE9GXVNCdrd90,987
|
||||
apscheduler/schedulers/qt.py,sha256=6BHOCi8e6L3wXTWwQDjNl8w_GJF_dY6iiO3gEtCJgmI,1241
|
||||
apscheduler/schedulers/tornado.py,sha256=dQBQKrTtZLPHuhuzZgrT-laU-estPRWGv9W9kgZETnY,1890
|
||||
apscheduler/schedulers/twisted.py,sha256=sRkI3hosp-OCLVluR_-wZFCz9auxqqWYauZhtOAoRU4,1778
|
||||
apscheduler/triggers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
apscheduler/triggers/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/triggers/__pycache__/base.cpython-311.pyc,,
|
||||
apscheduler/triggers/__pycache__/calendarinterval.cpython-311.pyc,,
|
||||
apscheduler/triggers/__pycache__/combining.cpython-311.pyc,,
|
||||
apscheduler/triggers/__pycache__/date.cpython-311.pyc,,
|
||||
apscheduler/triggers/__pycache__/interval.cpython-311.pyc,,
|
||||
apscheduler/triggers/base.py,sha256=8iKllubaexF456IK9jfi56QTrVIfDDPLavUc8wTlnL0,1333
|
||||
apscheduler/triggers/calendarinterval.py,sha256=BaH5rbTSVbPk3VhFwA3zORLSuZtYmFudS8GF0YxB51E,7411
|
||||
apscheduler/triggers/combining.py,sha256=LO0YKgBk8V5YfQ-L3qh8Fb6w0BvNOBghTFeAvZx3_P8,4044
|
||||
apscheduler/triggers/cron/__init__.py,sha256=ByWq4Q96gUWr4AwKoRRA9BD5ZVBvwQ6BtQMhafdStjw,9753
|
||||
apscheduler/triggers/cron/__pycache__/__init__.cpython-311.pyc,,
|
||||
apscheduler/triggers/cron/__pycache__/expressions.cpython-311.pyc,,
|
||||
apscheduler/triggers/cron/__pycache__/fields.cpython-311.pyc,,
|
||||
apscheduler/triggers/cron/expressions.py,sha256=89n_HxA0826xBJb8RprVzUDECs0dnZ_rX2wVkVsq6l8,9056
|
||||
apscheduler/triggers/cron/fields.py,sha256=RVbf6Lcyvg-3CqNzEZsfxzQ_weONCIiq5LGDzA3JUAw,3618
|
||||
apscheduler/triggers/date.py,sha256=ZS_TMjUCSldqlZsUUjlwvuWeMKeDXqqAMcZVFGYpam4,1698
|
||||
apscheduler/triggers/interval.py,sha256=u6XLrxlaWA41zvIByQvRLHTAuvkibG2fAZAxrWK3118,4679
|
||||
apscheduler/util.py,sha256=Lz2ddoeIpufXzW-HWnW5J08ijkXWGElDLVJf0DiPa84,13564
|
||||
@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.6.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
[apscheduler.executors]
|
||||
asyncio = apscheduler.executors.asyncio:AsyncIOExecutor
|
||||
debug = apscheduler.executors.debug:DebugExecutor
|
||||
gevent = apscheduler.executors.gevent:GeventExecutor
|
||||
processpool = apscheduler.executors.pool:ProcessPoolExecutor
|
||||
threadpool = apscheduler.executors.pool:ThreadPoolExecutor
|
||||
tornado = apscheduler.executors.tornado:TornadoExecutor
|
||||
twisted = apscheduler.executors.twisted:TwistedExecutor
|
||||
|
||||
[apscheduler.jobstores]
|
||||
etcd = apscheduler.jobstores.etcd:EtcdJobStore
|
||||
memory = apscheduler.jobstores.memory:MemoryJobStore
|
||||
mongodb = apscheduler.jobstores.mongodb:MongoDBJobStore
|
||||
redis = apscheduler.jobstores.redis:RedisJobStore
|
||||
rethinkdb = apscheduler.jobstores.rethinkdb:RethinkDBJobStore
|
||||
sqlalchemy = apscheduler.jobstores.sqlalchemy:SQLAlchemyJobStore
|
||||
zookeeper = apscheduler.jobstores.zookeeper:ZooKeeperJobStore
|
||||
|
||||
[apscheduler.triggers]
|
||||
and = apscheduler.triggers.combining:AndTrigger
|
||||
calendarinterval = apscheduler.triggers.calendarinterval:CalendarIntervalTrigger
|
||||
cron = apscheduler.triggers.cron:CronTrigger
|
||||
date = apscheduler.triggers.date:DateTrigger
|
||||
interval = apscheduler.triggers.interval:IntervalTrigger
|
||||
or = apscheduler.triggers.combining:OrTrigger
|
||||
@ -1 +0,0 @@
|
||||
apscheduler
|
||||
@ -1 +0,0 @@
|
||||
pip
|
||||
@ -1,20 +0,0 @@
|
||||
Copyright (c) 2017-2021 Ingy döt Net
|
||||
Copyright (c) 2006-2016 Kirill Simonov
|
||||
|
||||
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.
|
||||
@ -1,46 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: PyYAML
|
||||
Version: 6.0.2
|
||||
Summary: YAML parser and emitter for Python
|
||||
Home-page: https://pyyaml.org/
|
||||
Download-URL: https://pypi.org/project/PyYAML/
|
||||
Author: Kirill Simonov
|
||||
Author-email: xi@resolvent.net
|
||||
License: MIT
|
||||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
||||
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
||||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
||||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
||||
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
||||
Platform: Any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Cython
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
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 :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Text Processing :: Markup
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENSE
|
||||
|
||||
YAML is a data serialization format designed for human readability
|
||||
and interaction with scripting languages. PyYAML is a YAML parser
|
||||
and emitter for Python.
|
||||
|
||||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
||||
support, capable extension API, and sensible error messages. PyYAML
|
||||
supports standard YAML tags and provides Python-specific tags that
|
||||
allow to represent an arbitrary Python object.
|
||||
|
||||
PyYAML is applicable for a broad range of tasks from complex
|
||||
configuration files to object serialization and persistence.
|
||||
@ -1,44 +0,0 @@
|
||||
PyYAML-6.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
PyYAML-6.0.2.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
||||
PyYAML-6.0.2.dist-info/METADATA,sha256=9lwXqTOrXPts-jI2Lo5UwuaAYo0hiRA0BZqjch0WjAk,2106
|
||||
PyYAML-6.0.2.dist-info/RECORD,,
|
||||
PyYAML-6.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
PyYAML-6.0.2.dist-info/WHEEL,sha256=yEpuRje-u1Z_HrXQj-UTAfIAegW_HcP2GJ7Ek8BJkUM,102
|
||||
PyYAML-6.0.2.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
||||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
||||
_yaml/__pycache__/__init__.cpython-311.pyc,,
|
||||
yaml/__init__.py,sha256=N35S01HMesFTe0aRRMWkPj0Pa8IEbHpE9FK7cr5Bdtw,12311
|
||||
yaml/__pycache__/__init__.cpython-311.pyc,,
|
||||
yaml/__pycache__/composer.cpython-311.pyc,,
|
||||
yaml/__pycache__/constructor.cpython-311.pyc,,
|
||||
yaml/__pycache__/cyaml.cpython-311.pyc,,
|
||||
yaml/__pycache__/dumper.cpython-311.pyc,,
|
||||
yaml/__pycache__/emitter.cpython-311.pyc,,
|
||||
yaml/__pycache__/error.cpython-311.pyc,,
|
||||
yaml/__pycache__/events.cpython-311.pyc,,
|
||||
yaml/__pycache__/loader.cpython-311.pyc,,
|
||||
yaml/__pycache__/nodes.cpython-311.pyc,,
|
||||
yaml/__pycache__/parser.cpython-311.pyc,,
|
||||
yaml/__pycache__/reader.cpython-311.pyc,,
|
||||
yaml/__pycache__/representer.cpython-311.pyc,,
|
||||
yaml/__pycache__/resolver.cpython-311.pyc,,
|
||||
yaml/__pycache__/scanner.cpython-311.pyc,,
|
||||
yaml/__pycache__/serializer.cpython-311.pyc,,
|
||||
yaml/__pycache__/tokens.cpython-311.pyc,,
|
||||
yaml/_yaml.cp311-win_amd64.pyd,sha256=6BXrc7YC-BZJ911z64UDwJV3D0ay3GiyETPzbhl0iJc,272384
|
||||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
||||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
||||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
||||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
||||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
||||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
||||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
||||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
||||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
||||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
||||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
||||
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
|
||||
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
|
||||
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
|
||||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
||||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
||||
@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.44.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp311-cp311-win_amd64
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
_yaml
|
||||
yaml
|
||||
@ -1 +0,0 @@
|
||||
This is a dummy package designed to prevent namesquatting on PyPI. You should install `beautifulsoup4 <https://pypi.python.org/pypi/beautifulsoup4>`_ instead.
|
||||
@ -1 +0,0 @@
|
||||
pip
|
||||
@ -1,19 +0,0 @@
|
||||
Copyright 2005-2024 SQLAlchemy authors and contributors <see AUTHORS file>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,243 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: SQLAlchemy
|
||||
Version: 2.0.36
|
||||
Summary: Database Abstraction Library
|
||||
Home-page: https://www.sqlalchemy.org
|
||||
Author: Mike Bayer
|
||||
Author-email: mike_mp@zzzcomputing.com
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://docs.sqlalchemy.org
|
||||
Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Database :: Front-Ends
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
Requires-Dist: typing-extensions >=4.6.0
|
||||
Requires-Dist: greenlet !=0.4.17 ; python_version < "3.13" and (platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32"))))))
|
||||
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
||||
Provides-Extra: aiomysql
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'aiomysql'
|
||||
Requires-Dist: aiomysql >=0.2.0 ; extra == 'aiomysql'
|
||||
Provides-Extra: aioodbc
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'aioodbc'
|
||||
Requires-Dist: aioodbc ; extra == 'aioodbc'
|
||||
Provides-Extra: aiosqlite
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'aiosqlite'
|
||||
Requires-Dist: aiosqlite ; extra == 'aiosqlite'
|
||||
Requires-Dist: typing-extensions !=3.10.0.1 ; extra == 'aiosqlite'
|
||||
Provides-Extra: asyncio
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncio'
|
||||
Provides-Extra: asyncmy
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncmy'
|
||||
Requires-Dist: asyncmy !=0.2.4,!=0.2.6,>=0.2.3 ; extra == 'asyncmy'
|
||||
Provides-Extra: mariadb_connector
|
||||
Requires-Dist: mariadb !=1.1.10,!=1.1.2,!=1.1.5,>=1.0.1 ; extra == 'mariadb_connector'
|
||||
Provides-Extra: mssql
|
||||
Requires-Dist: pyodbc ; extra == 'mssql'
|
||||
Provides-Extra: mssql_pymssql
|
||||
Requires-Dist: pymssql ; extra == 'mssql_pymssql'
|
||||
Provides-Extra: mssql_pyodbc
|
||||
Requires-Dist: pyodbc ; extra == 'mssql_pyodbc'
|
||||
Provides-Extra: mypy
|
||||
Requires-Dist: mypy >=0.910 ; extra == 'mypy'
|
||||
Provides-Extra: mysql
|
||||
Requires-Dist: mysqlclient >=1.4.0 ; extra == 'mysql'
|
||||
Provides-Extra: mysql_connector
|
||||
Requires-Dist: mysql-connector-python ; extra == 'mysql_connector'
|
||||
Provides-Extra: oracle
|
||||
Requires-Dist: cx-oracle >=8 ; extra == 'oracle'
|
||||
Provides-Extra: oracle_oracledb
|
||||
Requires-Dist: oracledb >=1.0.1 ; extra == 'oracle_oracledb'
|
||||
Provides-Extra: postgresql
|
||||
Requires-Dist: psycopg2 >=2.7 ; extra == 'postgresql'
|
||||
Provides-Extra: postgresql_asyncpg
|
||||
Requires-Dist: greenlet !=0.4.17 ; extra == 'postgresql_asyncpg'
|
||||
Requires-Dist: asyncpg ; extra == 'postgresql_asyncpg'
|
||||
Provides-Extra: postgresql_pg8000
|
||||
Requires-Dist: pg8000 >=1.29.1 ; extra == 'postgresql_pg8000'
|
||||
Provides-Extra: postgresql_psycopg
|
||||
Requires-Dist: psycopg >=3.0.7 ; extra == 'postgresql_psycopg'
|
||||
Provides-Extra: postgresql_psycopg2binary
|
||||
Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary'
|
||||
Provides-Extra: postgresql_psycopg2cffi
|
||||
Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi'
|
||||
Provides-Extra: postgresql_psycopgbinary
|
||||
Requires-Dist: psycopg[binary] >=3.0.7 ; extra == 'postgresql_psycopgbinary'
|
||||
Provides-Extra: pymysql
|
||||
Requires-Dist: pymysql ; extra == 'pymysql'
|
||||
Provides-Extra: sqlcipher
|
||||
Requires-Dist: sqlcipher3-binary ; extra == 'sqlcipher'
|
||||
|
||||
SQLAlchemy
|
||||
==========
|
||||
|
||||
|PyPI| |Python| |Downloads|
|
||||
|
||||
.. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy
|
||||
:target: https://pypi.org/project/sqlalchemy
|
||||
:alt: PyPI
|
||||
|
||||
.. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy
|
||||
:target: https://pypi.org/project/sqlalchemy
|
||||
:alt: PyPI - Python Version
|
||||
|
||||
.. |Downloads| image:: https://static.pepy.tech/badge/sqlalchemy/month
|
||||
:target: https://pepy.tech/project/sqlalchemy
|
||||
:alt: PyPI - Downloads
|
||||
|
||||
|
||||
The Python SQL Toolkit and Object Relational Mapper
|
||||
|
||||
Introduction
|
||||
-------------
|
||||
|
||||
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper
|
||||
that gives application developers the full power and
|
||||
flexibility of SQL. SQLAlchemy provides a full suite
|
||||
of well known enterprise-level persistence patterns,
|
||||
designed for efficient and high-performing database
|
||||
access, adapted into a simple and Pythonic domain
|
||||
language.
|
||||
|
||||
Major SQLAlchemy features include:
|
||||
|
||||
* An industrial strength ORM, built
|
||||
from the core on the identity map, unit of work,
|
||||
and data mapper patterns. These patterns
|
||||
allow transparent persistence of objects
|
||||
using a declarative configuration system.
|
||||
Domain models
|
||||
can be constructed and manipulated naturally,
|
||||
and changes are synchronized with the
|
||||
current transaction automatically.
|
||||
* A relationally-oriented query system, exposing
|
||||
the full range of SQL's capabilities
|
||||
explicitly, including joins, subqueries,
|
||||
correlation, and most everything else,
|
||||
in terms of the object model.
|
||||
Writing queries with the ORM uses the same
|
||||
techniques of relational composition you use
|
||||
when writing SQL. While you can drop into
|
||||
literal SQL at any time, it's virtually never
|
||||
needed.
|
||||
* A comprehensive and flexible system
|
||||
of eager loading for related collections and objects.
|
||||
Collections are cached within a session,
|
||||
and can be loaded on individual access, all
|
||||
at once using joins, or by query per collection
|
||||
across the full result set.
|
||||
* A Core SQL construction system and DBAPI
|
||||
interaction layer. The SQLAlchemy Core is
|
||||
separate from the ORM and is a full database
|
||||
abstraction layer in its own right, and includes
|
||||
an extensible Python-based SQL expression
|
||||
language, schema metadata, connection pooling,
|
||||
type coercion, and custom types.
|
||||
* All primary and foreign key constraints are
|
||||
assumed to be composite and natural. Surrogate
|
||||
integer primary keys are of course still the
|
||||
norm, but SQLAlchemy never assumes or hardcodes
|
||||
to this model.
|
||||
* Database introspection and generation. Database
|
||||
schemas can be "reflected" in one step into
|
||||
Python structures representing database metadata;
|
||||
those same structures can then generate
|
||||
CREATE statements right back out - all within
|
||||
the Core, independent of the ORM.
|
||||
|
||||
SQLAlchemy's philosophy:
|
||||
|
||||
* SQL databases behave less and less like object
|
||||
collections the more size and performance start to
|
||||
matter; object collections behave less and less like
|
||||
tables and rows the more abstraction starts to matter.
|
||||
SQLAlchemy aims to accommodate both of these
|
||||
principles.
|
||||
* An ORM doesn't need to hide the "R". A relational
|
||||
database provides rich, set-based functionality
|
||||
that should be fully exposed. SQLAlchemy's
|
||||
ORM provides an open-ended set of patterns
|
||||
that allow a developer to construct a custom
|
||||
mediation layer between a domain model and
|
||||
a relational schema, turning the so-called
|
||||
"object relational impedance" issue into
|
||||
a distant memory.
|
||||
* The developer, in all cases, makes all decisions
|
||||
regarding the design, structure, and naming conventions
|
||||
of both the object model as well as the relational
|
||||
schema. SQLAlchemy only provides the means
|
||||
to automate the execution of these decisions.
|
||||
* With SQLAlchemy, there's no such thing as
|
||||
"the ORM generated a bad query" - you
|
||||
retain full control over the structure of
|
||||
queries, including how joins are organized,
|
||||
how subqueries and correlation is used, what
|
||||
columns are requested. Everything SQLAlchemy
|
||||
does is ultimately the result of a developer-initiated
|
||||
decision.
|
||||
* Don't use an ORM if the problem doesn't need one.
|
||||
SQLAlchemy consists of a Core and separate ORM
|
||||
component. The Core offers a full SQL expression
|
||||
language that allows Pythonic construction
|
||||
of SQL constructs that render directly to SQL
|
||||
strings for a target database, returning
|
||||
result sets that are essentially enhanced DBAPI
|
||||
cursors.
|
||||
* Transactions should be the norm. With SQLAlchemy's
|
||||
ORM, nothing goes to permanent storage until
|
||||
commit() is called. SQLAlchemy encourages applications
|
||||
to create a consistent means of delineating
|
||||
the start and end of a series of operations.
|
||||
* Never render a literal value in a SQL statement.
|
||||
Bound parameters are used to the greatest degree
|
||||
possible, allowing query optimizers to cache
|
||||
query plans effectively and making SQL injection
|
||||
attacks a non-issue.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Latest documentation is at:
|
||||
|
||||
https://www.sqlalchemy.org/docs/
|
||||
|
||||
Installation / Requirements
|
||||
---------------------------
|
||||
|
||||
Full documentation for installation is at
|
||||
`Installation <https://www.sqlalchemy.org/docs/intro.html#installation>`_.
|
||||
|
||||
Getting Help / Development / Bug reporting
|
||||
------------------------------------------
|
||||
|
||||
Please refer to the `SQLAlchemy Community Guide <https://www.sqlalchemy.org/support.html>`_.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Above all, SQLAlchemy places great emphasis on polite, thoughtful, and
|
||||
constructive communication between users and developers.
|
||||
Please see our current Code of Conduct at
|
||||
`Code of Conduct <https://www.sqlalchemy.org/codeofconduct.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
SQLAlchemy is distributed under the `MIT license
|
||||
<https://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
@ -1,530 +0,0 @@
|
||||
SQLAlchemy-2.0.36.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
SQLAlchemy-2.0.36.dist-info/LICENSE,sha256=eYQKk6tEYK_iQW6ePf95YIdsg66dK-JwXoOhBNSXQOs,1119
|
||||
SQLAlchemy-2.0.36.dist-info/METADATA,sha256=grqtAnGnYnQIU7y0X_EVSJjbaTjCEG1mMGnLYhj3Za4,9935
|
||||
SQLAlchemy-2.0.36.dist-info/RECORD,,
|
||||
SQLAlchemy-2.0.36.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
SQLAlchemy-2.0.36.dist-info/WHEEL,sha256=qW4RD1rfHm8ZRUjJbXUnZHDNPCXHt6Rq0mgR8lv_JEg,101
|
||||
SQLAlchemy-2.0.36.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
|
||||
sqlalchemy/__init__.py,sha256=PAN5IVcMVwzS9r2V524URkvNSjsiylsHp72xmxuVEvM,13327
|
||||
sqlalchemy/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/events.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/exc.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/inspection.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/log.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/schema.cpython-311.pyc,,
|
||||
sqlalchemy/__pycache__/types.cpython-311.pyc,,
|
||||
sqlalchemy/connectors/__init__.py,sha256=A2AI8p63aT0jT5CsVX33xlTfiGWliOcGahlK0RyTLXg,494
|
||||
sqlalchemy/connectors/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/aioodbc.cpython-311.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/asyncio.cpython-311.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/pyodbc.cpython-311.pyc,,
|
||||
sqlalchemy/connectors/aioodbc.py,sha256=fg3xfG-5gLsy-DSyVonNNKYhOf0_lzHmixRFa5edtWI,5462
|
||||
sqlalchemy/connectors/asyncio.py,sha256=EWjnlej7KeY7kDep36_ayJKl06_YnnOPWtUQKBZhgsY,6351
|
||||
sqlalchemy/connectors/pyodbc.py,sha256=IG5lLCyFbnv1wB85HQuMO3S5piWHaB660OBWvBIQhbg,8750
|
||||
sqlalchemy/cyextension/__init__.py,sha256=Hlfk91RinbOuNF_fybR5R2UtiIcTeUOXS66QOfSSCV0,250
|
||||
sqlalchemy/cyextension/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/cyextension/collections.cp311-win_amd64.pyd,sha256=wQCKVM_Y1pc6qWQMokjHs3B_HD_zMdrSxzpYfcEopks,175616
|
||||
sqlalchemy/cyextension/collections.pyx,sha256=GXPkr9cHRLW3Vcu-ik3dVBZMR-zf0Q5_K4J-_8yV-gk,12980
|
||||
sqlalchemy/cyextension/immutabledict.cp311-win_amd64.pyd,sha256=5yHSG99wedmaRjwv-SIPMB8TFsDtkqNqBfS-6RMGM2U,73216
|
||||
sqlalchemy/cyextension/immutabledict.pxd,sha256=5iGndSbJCgCkNmRbJ_z14RANs2dSSnAzyiRPUTBk58Y,299
|
||||
sqlalchemy/cyextension/immutabledict.pyx,sha256=IhB2pR49CrORXQ3LXMFpuCIRc6I08QNvIylE1cPQA5o,3668
|
||||
sqlalchemy/cyextension/processors.cp311-win_amd64.pyd,sha256=aQKjVv4NeM8dpBsOsRg4MdMw4z0Kv0A2LMH7y96a0LI,58880
|
||||
sqlalchemy/cyextension/processors.pyx,sha256=V9gzqXiNHWsa5DBgYl-3KzclFHY8kXGF_TD1xHFE7eM,1860
|
||||
sqlalchemy/cyextension/resultproxy.cp311-win_amd64.pyd,sha256=DHaO1-w221oyCGEiV8B7JTz8i-hXJW6CKEVBkkr-THU,60928
|
||||
sqlalchemy/cyextension/resultproxy.pyx,sha256=h_RrKasbLtKK3LqUh6UiWtkumBlKtcN5eeB_1bZROMA,2827
|
||||
sqlalchemy/cyextension/util.cp311-win_amd64.pyd,sha256=ms_vCfxgVanYMSxIOLWaof3gdV_IKP8vJ85E3IDQ4zU,73216
|
||||
sqlalchemy/cyextension/util.pyx,sha256=50QYpSAKgLSUfhFEQgSN2e1qHWCMh_b6ZNlErDUS7ec,2621
|
||||
sqlalchemy/dialects/__init__.py,sha256=SJfQyxMhOL58EB-S6GQv_0jf2oP7MMfmVdlV2UxGWQo,1831
|
||||
sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/__pycache__/_typing.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/_typing.py,sha256=mN2r8mU8z-mRh4YS3VeK8Nv_IKJmE0Mb1CrJ-ptILas,913
|
||||
sqlalchemy/dialects/mssql/__init__.py,sha256=r3oTfX2LLbJAGhM57wdPLWxaZBzunkcmyaTbW0FjLuY,1968
|
||||
sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mssql/aioodbc.py,sha256=b9bhUKcVj4NzoqJIDfECeE_Rmt51sRy8OOUFz_R3vpg,2086
|
||||
sqlalchemy/dialects/mssql/base.py,sha256=EBnNJv8RFOTci5tpgK2tUU_JUyGdew25j4edOosECj8,136433
|
||||
sqlalchemy/dialects/mssql/information_schema.py,sha256=A1UJAoFb3UtE8YCY3heBgeTMkzWq3j7C2caZ3gcMGZk,8338
|
||||
sqlalchemy/dialects/mssql/json.py,sha256=nZVVsgmR4Z4dNn9cv5Gucq596gsQ0MvASPuEEtz-Gek,4949
|
||||
sqlalchemy/dialects/mssql/provision.py,sha256=jpyErAQ_zgmQKiE0G8dQcNn6O8ssQELrO2BzcukLUa0,5755
|
||||
sqlalchemy/dialects/mssql/pymssql.py,sha256=RrxWm94UgCipZCM8FbeeENFndJh6JDaSkWPF3feK_VI,4223
|
||||
sqlalchemy/dialects/mssql/pyodbc.py,sha256=YVI19AnrqxPCBwDqcjrO_rqUUWbV2re7E8iLuV1ilqE,27801
|
||||
sqlalchemy/dialects/mysql/__init__.py,sha256=PPQDwNqcpxWMt3nFQ66KefX9T9iz7d8lybEwKlfXB1U,2254
|
||||
sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/mysql/aiomysql.py,sha256=hZg2W3C_O2ExL5bVlcqmDX0vZbVR2dMyuGqTGVlBzig,10332
|
||||
sqlalchemy/dialects/mysql/asyncmy.py,sha256=xnYyNzk6JJVzxpxu4B7GhUP9CR5rtHVw8mGoWsHAwT8,10404
|
||||
sqlalchemy/dialects/mysql/base.py,sha256=yNKJCAwsapLTK5GBQZfzPA4DvTqee8_US9VY3brMwqI,126249
|
||||
sqlalchemy/dialects/mysql/cymysql.py,sha256=0mRP3gFe2t7iJYQqJz1Os_TztFwMAF34w2MmXe-4B_w,2384
|
||||
sqlalchemy/dialects/mysql/dml.py,sha256=n31-m4vfOIL0MdHpUdIfTLgaMzusfQ-yHYoJWO_ndEc,7864
|
||||
sqlalchemy/dialects/mysql/enumerated.py,sha256=Nz9Sv3ENX-1T18aEoOY8QfZlAcwRf65lIOse7vwjil8,8692
|
||||
sqlalchemy/dialects/mysql/expression.py,sha256=uxD1fICubfGh8BhAn6WoeS8AF6hAVEvreDShXqRZTqM,4238
|
||||
sqlalchemy/dialects/mysql/json.py,sha256=i0Lrd_7VKTd3fNm6kQKzrtPERuW0JeSw7XSUWnl1HQI,2350
|
||||
sqlalchemy/dialects/mysql/mariadb.py,sha256=WoNxkjiPfIbWAkrVEU9MTM7mePeLHZ2uiJsyfvcpv1s,885
|
||||
sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=wRuM0PPLnK1avjQnvbgC7pLf54JTondEpi-sffDWPMM,8900
|
||||
sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=w4a36lSTM8JHgohUoOsjqqaWh3822207gGUFGovkSRo,5909
|
||||
sqlalchemy/dialects/mysql/mysqldb.py,sha256=2aFRw8C0LdU7hk6zjXDijEY8UeWK360TB8pTMZ5Ddto,9806
|
||||
sqlalchemy/dialects/mysql/provision.py,sha256=ikb3a6XUnMa51JLmlIbDr9QLvTs3QtdyldHn-lME-qI,3685
|
||||
sqlalchemy/dialects/mysql/pymysql.py,sha256=Kxi_A34-nbQ5UEFSmy14TXc1v43-1SZ8gE628REGTFo,4220
|
||||
sqlalchemy/dialects/mysql/pyodbc.py,sha256=CZCEnhyLIgbuiAW32Cw7N1m1aiQv1eBB34pV-txOs70,4435
|
||||
sqlalchemy/dialects/mysql/reflection.py,sha256=HQOQbNu9o977ki2Xpb7AbZ_mhAR9EK3YOGp3HoezJxg,23511
|
||||
sqlalchemy/dialects/mysql/reserved_words.py,sha256=qzej7CIVFz2Q2ywue7nKL59cca2kzXhKpDOSQYMlxjU,9829
|
||||
sqlalchemy/dialects/mysql/types.py,sha256=wqfI5QZ8__Uzn9cYefTMZ387cJJJgbNkCDE9Ax2k1pY,25117
|
||||
sqlalchemy/dialects/oracle/__init__.py,sha256=_yFT_k0R6yc7MKQG-Al9QZt8wYZsiCtpkhNlba5xqn8,1560
|
||||
sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/types.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/oracle/base.py,sha256=bigUmqUbddoa9vCYvrkajlDiYsFhNdAIpkfi7q0JtzY,122947
|
||||
sqlalchemy/dialects/oracle/cx_oracle.py,sha256=5DyLekZ1IV1-KQdtMHEyt-AtHDq1RLX-XN-N6zPEywA,56718
|
||||
sqlalchemy/dialects/oracle/dictionary.py,sha256=tmAZLEACqBAPBE0SEV2jr1R4aPcpNOrbomJl-UmgiR4,20026
|
||||
sqlalchemy/dialects/oracle/oracledb.py,sha256=nWB80zaRoUl9uJ3rjvCnvifeqfNiCzFmDi6uSr6wEkk,14050
|
||||
sqlalchemy/dialects/oracle/provision.py,sha256=KKlXDQnC8n6BjLJWA7AJg3lwXluH1OyStqfP2Uf9rq0,8524
|
||||
sqlalchemy/dialects/oracle/types.py,sha256=U9EReFRcr0PiwOxT9vg2cA7WOix8LQ2sVp0gRkMHcPo,8518
|
||||
sqlalchemy/dialects/postgresql/__init__.py,sha256=C0BhKzUkClwGfAetbBIEd4KQohOtSmfHo5TwhoZLCK0,4059
|
||||
sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/types.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=fYFqLVxNxAqh3nOvzGOv3Pfpm2BsclHrk71MJZrpJKo,5883
|
||||
sqlalchemy/dialects/postgresql/array.py,sha256=_vzfyGBY1NsT6blooCgHrLC38VZbM4UWKQHgXLmmyYs,14159
|
||||
sqlalchemy/dialects/postgresql/asyncpg.py,sha256=mT_-cDcHx_ERtn8LEZEiafOhrBySKEkwnrdq7grSGj0,42348
|
||||
sqlalchemy/dialects/postgresql/base.py,sha256=Tf0ADIFFeuIuPmFhtcvw7gSj4WOq65efZ_bxd-puXWU,184091
|
||||
sqlalchemy/dialects/postgresql/dml.py,sha256=uMiqxEkji-UXqk8gO1ramQEvEfCugYmy8Cv1cnG7DQs,11522
|
||||
sqlalchemy/dialects/postgresql/ext.py,sha256=ct6NQfMAfBnLYhybpF2wPEq-p8-U0tEpy-aq8NwqJLw,16758
|
||||
sqlalchemy/dialects/postgresql/hstore.py,sha256=4jAZQMPWl3VE4weDRZrgrbVDRZJTM3X0Xj4twr5znYQ,11938
|
||||
sqlalchemy/dialects/postgresql/json.py,sha256=uYTzL3gECnQ8MGlH76MG7lgOzudWJwEVAukzVBeEaUw,11951
|
||||
sqlalchemy/dialects/postgresql/named_types.py,sha256=Ykl4GWSf5pQynkAWfZsAjgYU0R_TSvEvaZrb8mI4PuQ,18103
|
||||
sqlalchemy/dialects/postgresql/operators.py,sha256=iyZuyx_daRyJjiS5rw-XnZlaWj1bmRiHdy5MXzBrFZw,2937
|
||||
sqlalchemy/dialects/postgresql/pg8000.py,sha256=TPJXX078vW0FSwZ-DlWNkEOXg7Z4xk8IFwi1droMhPw,19302
|
||||
sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=rG_AGLtjSQ6DAnkqAiurYpnIuLhN9Ib_QydWbmjK--s,9554
|
||||
sqlalchemy/dialects/postgresql/provision.py,sha256=8ieCPOOsTLLcf2qa4zasAu7UCJ9S7rK5xl2_u_OkB2w,5945
|
||||
sqlalchemy/dialects/postgresql/psycopg.py,sha256=MtS8vtJB0jStB1nILco6STUfdHv4m8rbWisXBzVXiO4,23940
|
||||
sqlalchemy/dialects/postgresql/psycopg2.py,sha256=Jczz-5NGNYlkmkDlJYCpXRQSmNszqgZZaX6yqwc0vvg,32884
|
||||
sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=hFg-9GH08ApPy3foVPUdJKwCEzNSv2zD5l4nH97AqgI,1817
|
||||
sqlalchemy/dialects/postgresql/ranges.py,sha256=oiTmnZ-hd5WqqGNsXbuOJfoNxpbso_M_49gky8dlCrE,33978
|
||||
sqlalchemy/dialects/postgresql/types.py,sha256=pd1QmuGwJFLqpY2tK-Ql3FNjtT1Ha-lVvfaR9dimvHc,7603
|
||||
sqlalchemy/dialects/sqlite/__init__.py,sha256=MmQfjHun1U_4q-Dq_yhs9RzAX0VLixSwWeY5xWiDwag,1239
|
||||
sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc,,
|
||||
sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=q9NcdCMnlOMiX2ySw-lqzZB0X8_nYoCmHES9SiIy9v0,12741
|
||||
sqlalchemy/dialects/sqlite/base.py,sha256=SCliffPlfv08INhTwnGhYUYLmxI5aqiMgeoqLnlZB20,100616
|
||||
sqlalchemy/dialects/sqlite/dml.py,sha256=8JV6Ise7WtmFniy590X5b19AYZcE51M6N5hef7d9JoA,8683
|
||||
sqlalchemy/dialects/sqlite/json.py,sha256=-9afZnBt07vInCX20CKzjlTG85wHTO5_cxhcYU4phDc,2869
|
||||
sqlalchemy/dialects/sqlite/provision.py,sha256=nAXZPEjXFrb6a1LxXZMqKmkQoXgl3MPsSHuMyBQ76NU,5830
|
||||
sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=p0KfzHBwANDMwKTKEJCjR5RxMYqQwS4E8KXjl3Bx6Fw,5511
|
||||
sqlalchemy/dialects/sqlite/pysqlite.py,sha256=TcApeaXMjcM-IMRNEp4sQn-lE3Z6khLvoe7L63k7TN4,28824
|
||||
sqlalchemy/dialects/type_migration_guidelines.txt,sha256=gyh3JCauAIFi_9XEfqm3vYv_jb2Eqcz2HjpmC9ZEPMM,8384
|
||||
sqlalchemy/engine/__init__.py,sha256=93FWhb62dLCidc6e4FE65wq_P8GeoWQG1OG6RZMBqhM,2880
|
||||
sqlalchemy/engine/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_processors.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_row.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_util.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/characteristics.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/create.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/cursor.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/default.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/events.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/interfaces.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/mock.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/processors.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/reflection.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/result.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/row.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/strategies.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/url.cpython-311.pyc,,
|
||||
sqlalchemy/engine/__pycache__/util.cpython-311.pyc,,
|
||||
sqlalchemy/engine/_py_processors.py,sha256=-jlAYPM6etmuKeViiI7BD41kqY0Pr8nzaox22TPqCCQ,3880
|
||||
sqlalchemy/engine/_py_row.py,sha256=UEGCjAeRsggcUn0QB0PdFC82kuykrOiOZ1KGq_Gf_qQ,3915
|
||||
sqlalchemy/engine/_py_util.py,sha256=nh1XoVq1b-eGgkdzbqFqzje0RNSmVWotoa6yaB7J5Sw,2558
|
||||
sqlalchemy/engine/base.py,sha256=ags97hnejh9awIFQ5t1RkHnZOSDJSUiSeaOqrPAU_4k,126333
|
||||
sqlalchemy/engine/characteristics.py,sha256=hfTuHv_WxSNHOS_LFIypSb27lqoZKRXIQKyT7ay5lTY,4920
|
||||
sqlalchemy/engine/create.py,sha256=-SCpvMx3DIwt8TD6Cyh_ChieKQ8y3hDD9YLDBIKgY6o,34081
|
||||
sqlalchemy/engine/cursor.py,sha256=zKN-AiwE-0KkvdWqhg8dOAT4R1U1a4I0tFgAqzihSnE,78573
|
||||
sqlalchemy/engine/default.py,sha256=vut1eoGPPFksx3iMTr1MNzLvM52GNgqzm9sZuvTVt24,87013
|
||||
sqlalchemy/engine/events.py,sha256=e0VHj69fH20sB7gocBhr5Rs2FjR8ioY4iE8VQt70oJg,38332
|
||||
sqlalchemy/engine/interfaces.py,sha256=a7kJmfaTRQgBvoUve_86CFI2tWGF-Htv_OQBJHCCQtU,116339
|
||||
sqlalchemy/engine/mock.py,sha256=wInBRiHwydTc5ELQLivdezDd1ikbSMVXgLVzZrSC0iQ,4310
|
||||
sqlalchemy/engine/processors.py,sha256=w4MiVMlU6VvfhIW49nygbHcwX8FteGpz7g3IGEqtZb8,2440
|
||||
sqlalchemy/engine/reflection.py,sha256=DYyhp221HdpnjtEKvIXoHFj6LvkUntMpK3gc7FwkhZ4,77462
|
||||
sqlalchemy/engine/result.py,sha256=ItLliL4T31CSD1cTgbVmliEQBtRtneDCqChAM00iiSM,80033
|
||||
sqlalchemy/engine/row.py,sha256=g7ZqmsqX_BtRUzY-zfXoZZ4-5xZ_KJEVbvqKHUIlqRg,12433
|
||||
sqlalchemy/engine/strategies.py,sha256=fD4DJn0AD371wlUa7s5Sy4j7QtgGyP7gMy_kUyqCLDQ,461
|
||||
sqlalchemy/engine/url.py,sha256=tOCRmKkqrpsIfNeSDoy6KKTLtQAMtoIn9xa5kmJQebk,31694
|
||||
sqlalchemy/engine/util.py,sha256=wIrPulEwr7kAxJ-vNj5QSPGNfWoFx5B4zzJdXu_ZIFg,5849
|
||||
sqlalchemy/event/__init__.py,sha256=09qZzHwt0PkIDsPwuPUVJvNakjtCBjuUJeY0AEJ9j7k,1022
|
||||
sqlalchemy/event/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/event/__pycache__/api.cpython-311.pyc,,
|
||||
sqlalchemy/event/__pycache__/attr.cpython-311.pyc,,
|
||||
sqlalchemy/event/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/event/__pycache__/legacy.cpython-311.pyc,,
|
||||
sqlalchemy/event/__pycache__/registry.cpython-311.pyc,,
|
||||
sqlalchemy/event/api.py,sha256=I7XWFczjgl3RcBH_52TbQ0S3_W4RZz1Be9dXkxtFw5U,8451
|
||||
sqlalchemy/event/attr.py,sha256=-SHjzXMOs7IICPSgNwpgRS3FIEeLIpB5PyvVlpw8Gp8,21406
|
||||
sqlalchemy/event/base.py,sha256=5JA45j3ncMWPXDeIPLJ_D5lX06Z3TJf8dla8bCriOkU,15597
|
||||
sqlalchemy/event/legacy.py,sha256=a8VEvS83PvgbomNnaSa3okZmTkxl_buZ7Lfilechjh8,8473
|
||||
sqlalchemy/event/registry.py,sha256=f31k0FLqIlWpOK9tksiYXnv-yuZPPz9iLQqvKEYV7ko,11221
|
||||
sqlalchemy/events.py,sha256=OAy8TK21lWzSe8bDUnAbmsP82bsBYy0LL19hR6y3BrM,542
|
||||
sqlalchemy/exc.py,sha256=k01TD2xp2BM3DrXdo2U5r8yuRfsoqBND4kwvtD1SVN0,24806
|
||||
sqlalchemy/ext/__init__.py,sha256=YbMQmRS_9HxRyWM-KA_F76WOss1_Em1ZcrnQDIDXoOc,333
|
||||
sqlalchemy/ext/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/associationproxy.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/automap.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/baked.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/compiler.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/horizontal_shard.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/hybrid.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/indexable.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/instrumentation.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/mutable.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/orderinglist.cpython-311.pyc,,
|
||||
sqlalchemy/ext/__pycache__/serializer.cpython-311.pyc,,
|
||||
sqlalchemy/ext/associationproxy.py,sha256=4eT25cDGBqCExY3ey1HtQv1n-EtFdTIXpRRN0bt2Ga4,68075
|
||||
sqlalchemy/ext/asyncio/__init__.py,sha256=tKYIrERYf8hov9m8DuKWRO_53qhrvj2jRmIYjSGQ2Po,1342
|
||||
sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/engine.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/exc.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/result.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/session.cpython-311.pyc,,
|
||||
sqlalchemy/ext/asyncio/base.py,sha256=slWQTFdgQQlkzrnx3m5a9xT8IRg4iM0gkEbypXr_YXQ,9184
|
||||
sqlalchemy/ext/asyncio/engine.py,sha256=HJ5IZD0_xfVOMEGYZ1XtDir73SpzBk6ODDUN75ltvzo,49656
|
||||
sqlalchemy/ext/asyncio/exc.py,sha256=0awLfUB4PhEPVVTKYluyor1tW91GPZZnvdQ-GGSOmJY,660
|
||||
sqlalchemy/ext/asyncio/result.py,sha256=2hCQKOjbmFCYfEjk33FY1ZUAPQaoDmgoIW8IzqE63Bg,31438
|
||||
sqlalchemy/ext/asyncio/scoping.py,sha256=XZqvqtIXpWujkjV7DBikIsQcjaVLei1C3LlflZxiZ_o,54222
|
||||
sqlalchemy/ext/asyncio/session.py,sha256=_9J2zd9-tc4YNKjbe6JQkMNXD_PI-l84CbHRNA0OSGE,65023
|
||||
sqlalchemy/ext/automap.py,sha256=dIo-IoF9t6xcrBj1EV5caHPmPISXXzH-637utGlqi00,63272
|
||||
sqlalchemy/ext/baked.py,sha256=jc6vPocoXXsvdZsOsqgT4kG6guWSZD1TdPjoRBmkbRU,18381
|
||||
sqlalchemy/ext/compiler.py,sha256=iIc87FNm-rgbGMsST5nskNkkQe1WE-cvpgd2g8zobnM,21447
|
||||
sqlalchemy/ext/declarative/__init__.py,sha256=MHSOffOS4MWcqshAuLNQv0vDXpK_Z3lpGXTm1riyLls,1883
|
||||
sqlalchemy/ext/declarative/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/extensions.cpython-311.pyc,,
|
||||
sqlalchemy/ext/declarative/extensions.py,sha256=aPpW0PvTKH3CoSMhsOY5GcUMZOVq-OFsV1hflxmb3Lw,20095
|
||||
sqlalchemy/ext/horizontal_shard.py,sha256=V8vXEt5ZQb_PM39agZD2IyoQNGSqVI1MhY-6mNV5MRY,17231
|
||||
sqlalchemy/ext/hybrid.py,sha256=UpWd9hOD5I3hCT5FJW9twSDyShDVyygRIFtv-FmMHgM,53972
|
||||
sqlalchemy/ext/indexable.py,sha256=aDlVpN4rilRrer9qKg3kO7fqnqB5NX4M5qzYuYM8pvw,11373
|
||||
sqlalchemy/ext/instrumentation.py,sha256=lFsJECWlN1oc1E0r9TaQDZcxAx4VOz6PSHYrl5fLk9Y,16157
|
||||
sqlalchemy/ext/mutable.py,sha256=nAz3_lF2xkYSARt7GAWQh-OUMcnpe6s1ocjvQGxCPkc,38428
|
||||
sqlalchemy/ext/mypy/__init__.py,sha256=aqT8_9sNwzC8PIaEZ4zkCYGBvYPaDD3eCgJtJuk3g6A,247
|
||||
sqlalchemy/ext/mypy/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/apply.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/infer.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/names.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/plugin.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/util.cpython-311.pyc,,
|
||||
sqlalchemy/ext/mypy/apply.py,sha256=1Qb-_FpQ_0LVB2KFA5hVjfPv6DDMIcxXe86Ts1X9GBk,10870
|
||||
sqlalchemy/ext/mypy/decl_class.py,sha256=f2iWiFVlDFqGb_IoGGotI3IEOUErh25sLT7B_cMfx0g,17899
|
||||
sqlalchemy/ext/mypy/infer.py,sha256=O-3IjELDSBEAwGGxRM7lr0NWwGD0HMK4vda_iY6iwjs,19959
|
||||
sqlalchemy/ext/mypy/names.py,sha256=2bHYuQJe71c9JtuJSZP-WWGiOJmY9-FuMQGMDBB6dRs,10814
|
||||
sqlalchemy/ext/mypy/plugin.py,sha256=TDTziLsYFRqyX8UcQMtBBa6TFR4z9N-XNO8wRkHlEOI,10053
|
||||
sqlalchemy/ext/mypy/util.py,sha256=s3-QXtuJ5lBZHdhpMdCa36EBV-wic5Dl1hFOB_2bVqw,10317
|
||||
sqlalchemy/ext/orderinglist.py,sha256=r7La_3nZlGevIgsBL1IB30FvWO_tZHlTKo_FWwid-aY,14800
|
||||
sqlalchemy/ext/serializer.py,sha256=4DuLXwWsuZU8vyFw2kOmuelSXozpCgr_pH36af1mtVE,6321
|
||||
sqlalchemy/future/__init__.py,sha256=6-qPdjMHX-V-kAPjTQgNuHztmYiwKlJhKhhljuETvoQ,528
|
||||
sqlalchemy/future/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/future/__pycache__/engine.cpython-311.pyc,,
|
||||
sqlalchemy/future/engine.py,sha256=N_5W2ab5-ueedWzqNdgLPzTW9audT1IbxF6FCDLRZOc,510
|
||||
sqlalchemy/inspection.py,sha256=GpmMuSAZ53u4W__iGpvzQKCBMFnTxnHt4Lo7Nq1FSKM,5237
|
||||
sqlalchemy/log.py,sha256=Sg6PGR_wmseiCCpJfRDEkaMs08XTPPsf0X_iYJLvzS0,8895
|
||||
sqlalchemy/orm/__init__.py,sha256=I-XesvuyjkAAwnsiF5FnXRLNV6W2nW70EnGAIt2GAjU,8633
|
||||
sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/_orm_constructors.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/_typing.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/bulk_persistence.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/collections.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/context.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/events.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/exc.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/identity.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/loading.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapped_collection.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/properties.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/query.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/session.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state_changes.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/sync.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/util.cpython-311.pyc,,
|
||||
sqlalchemy/orm/__pycache__/writeonly.cpython-311.pyc,,
|
||||
sqlalchemy/orm/_orm_constructors.py,sha256=7JCWOOTvMSa0drFcJVb8H2LP1Zme9EmklPtVPn2AKJA,106005
|
||||
sqlalchemy/orm/_typing.py,sha256=Z9GZT8Vb-wFwvHeOeVE37dvmCWdItLZnqI_pLin4cMc,5152
|
||||
sqlalchemy/orm/attributes.py,sha256=4PcKfGsqziKh-odMCSoLP37lCzVT27n8qrXuDEDZq74,95369
|
||||
sqlalchemy/orm/base.py,sha256=SKMbYGIgGcuqwQeteHN-G0sS1d_gx2dSxMjuZmBlYm0,28475
|
||||
sqlalchemy/orm/bulk_persistence.py,sha256=EZDT2IUFjuVLMra1urGiZSh99aWjUVrua0tBpz9f_lY,74786
|
||||
sqlalchemy/orm/clsregistry.py,sha256=2pE9BCnPEDvid_2zgFHllr2CTG_jLcx_ThdZo6zJ0ew,18545
|
||||
sqlalchemy/orm/collections.py,sha256=UOBbkTc4jCMbqM7fb9277VR1fCN5TRaFKV8pAcNyv78,53863
|
||||
sqlalchemy/orm/context.py,sha256=3egDf1kEB41tiNByqVj5_cUG4AcYDoYTZhxICBSAugA,116223
|
||||
sqlalchemy/orm/decl_api.py,sha256=i6mXKqNrCxu3bJKhhBt_KtsPZHVVDHemAFEQpUdQvAM,65881
|
||||
sqlalchemy/orm/decl_base.py,sha256=UpBoWt4f3mY6hsu46CFWXKju6c9qM-M5jHvSEHftgZ4,85533
|
||||
sqlalchemy/orm/dependency.py,sha256=glstmbB4t-PIRA47u9NgTyyxbENfyQuG9Uzj2iezB_s,48935
|
||||
sqlalchemy/orm/descriptor_props.py,sha256=uRIgZMIRFACONzgiXz6q6wdvahby2s1ogkjHARhEVFk,38320
|
||||
sqlalchemy/orm/dynamic.py,sha256=MMPj4Esn_CRweI8YMbYcl9nNwcMWB4O6owOaFgcIvB0,10116
|
||||
sqlalchemy/orm/evaluator.py,sha256=Uttss9NiHt12sWWjH4XfK8hzQANciaefXwFf7On2cns,12732
|
||||
sqlalchemy/orm/events.py,sha256=8K_DUstcftizTDyYomGXo_GLbYdUG526JYQa9b5D7DA,131038
|
||||
sqlalchemy/orm/exc.py,sha256=fd24WdW3CP3oxFcz9CLXPeBIAfqJZbKv7K4G-5X4EOg,7641
|
||||
sqlalchemy/orm/identity.py,sha256=fOpANTf73r12F_w9DhVoyjkAdh8ldgJcNnwxx0GY8YM,9551
|
||||
sqlalchemy/orm/instrumentation.py,sha256=a8vi3qEAyO7Z9PYksLkFi_YzxqQhzB-anblegiAtsFw,25075
|
||||
sqlalchemy/orm/interfaces.py,sha256=c7rAWvsF8xnae19U50OTvuzb_I4n88FH_FgqPtd_sr0,50164
|
||||
sqlalchemy/orm/loading.py,sha256=RCEUmTS--9QntLQcnwYVxHRsC3ILE58AVNHb8MGcybg,59959
|
||||
sqlalchemy/orm/mapped_collection.py,sha256=OjiojLC9caz_-0vt2289sS35bGOiM6GbxWywCg1RFZc,20239
|
||||
sqlalchemy/orm/mapper.py,sha256=5DItf0WognEURCJ3-tInhtYdijDN8jNZEwOns43z-C8,176170
|
||||
sqlalchemy/orm/path_registry.py,sha256=GQS4KatFTi_6LKdi6I4185igHE6DDwrm8b1AMOHMom4,26731
|
||||
sqlalchemy/orm/persistence.py,sha256=MKb7TuSLJUQpyqnHxf6uNmGXSznmZgkkFTD04nHbNUQ,63483
|
||||
sqlalchemy/orm/properties.py,sha256=DicF2OHW-b5z7xJrahELKd5OPm9cVfTIYjQcoEM1naw,30192
|
||||
sqlalchemy/orm/query.py,sha256=KgFWo2xCpF26VHRD3oRLNvbckekuORcn1LPTjguDCZ0,121104
|
||||
sqlalchemy/orm/relationships.py,sha256=tj00j1ISHQmJbWeegWn8U29lL6WUnqd8fdY7NRHcLWU,132144
|
||||
sqlalchemy/orm/scoping.py,sha256=Na86AVpI1bry0ICm67wXT7CmCffVu3Muoorn9c-hLNk,80853
|
||||
sqlalchemy/orm/session.py,sha256=EDUpkGw5rx66579JfvtME2AeqGsdh1V2nznuc3dgDfc,201280
|
||||
sqlalchemy/orm/state.py,sha256=TB9954ZttIUj4uMF-4tfocwaDaq9g1uCbgXuuk-VwJA,38813
|
||||
sqlalchemy/orm/state_changes.py,sha256=4i90vDgBGvVGUzhlonlBkZBAZFOWaAXij2X8OEA3-BA,7013
|
||||
sqlalchemy/orm/strategies.py,sha256=QcSrpN8wsobzKYThvbNhnbVhz6-AfUvUuPjKpeK1U9w,123339
|
||||
sqlalchemy/orm/strategy_options.py,sha256=uajUK7bpjJGl8ygbbuzChwXiJEUd6dEHtjXEnqY1dlw,87922
|
||||
sqlalchemy/orm/sync.py,sha256=aMEMhYTj2rtJZJvjqm-cUx2CoQxYl8P6YddCLpLelhM,5943
|
||||
sqlalchemy/orm/unitofwork.py,sha256=THggzzAaqmYh5PBDob5dHTP_YyHXYdscs3fIxtRV-gE,27829
|
||||
sqlalchemy/orm/util.py,sha256=Ju6SEzkmZvDM1N7jmYkB6pImZHRsCqn-k7Nj6Wlhgjg,83445
|
||||
sqlalchemy/orm/writeonly.py,sha256=j5DcpZKOv1tLGQLhKfk-Uw-B0yEG7LezwJWNTq0FtWQ,22983
|
||||
sqlalchemy/pool/__init__.py,sha256=ZKUPMKdBU57mhu677UsvRs5Aq9s9BwIbMmSNRoTRPoY,1848
|
||||
sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/pool/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/pool/__pycache__/events.cpython-311.pyc,,
|
||||
sqlalchemy/pool/__pycache__/impl.cpython-311.pyc,,
|
||||
sqlalchemy/pool/base.py,sha256=D0sKTRla6wpIFbELyGY2JEHUHR324rveIl93qjjmYr8,53751
|
||||
sqlalchemy/pool/events.py,sha256=ysyFh0mNDpL4N4rQ-o_BC6tpo_zt0_au_QLBgJqaKY8,13517
|
||||
sqlalchemy/pool/impl.py,sha256=BU5vUQ6NDFIldsG9og6mtO14SsqwpUqvwyGqZsKT6i0,19525
|
||||
sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/schema.py,sha256=UFhZjGmYoqN3zkId7M4CbVCd8KaeZUfKUjdlk0sHQ_E,3264
|
||||
sqlalchemy/sql/__init__.py,sha256=T16ZB3Za0Tq1LQGXeJuuxDkyu2t-XHR2t-8QH1mE1Uw,5965
|
||||
sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_dml_constructors.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_elements_constructors.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_orm_types.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_py_util.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_typing.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/cache_key.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/crud.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/dml.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/elements.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/events.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/expression.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/functions.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/naming.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/operators.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/roles.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/schema.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/util.cpython-311.pyc,,
|
||||
sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc,,
|
||||
sqlalchemy/sql/_dml_constructors.py,sha256=1xMH5Kd6SLhlFwfIs_lOXGC8GTrqW8mQM7Kc3cKyLuw,4007
|
||||
sqlalchemy/sql/_elements_constructors.py,sha256=FRFRwYB9X5w01gyf07QjdkNa0rH8GrJOFW9-4sxZn88,65018
|
||||
sqlalchemy/sql/_orm_types.py,sha256=_bzlAh3-vTIZoLvAM2ry1SF7rsYRM3-jupfhGWZZn5Y,645
|
||||
sqlalchemy/sql/_py_util.py,sha256=VzThcXk7fKqT9_mZmXrkxePdwyyl_wIciCftzl2Z_-g,2248
|
||||
sqlalchemy/sql/_selectable_constructors.py,sha256=mRgtlGyctlb1LMBqFxgn0eGzIXMbyZtQafjUuJWhYjs,19415
|
||||
sqlalchemy/sql/_typing.py,sha256=FzAwrbL9o2CMluxxj1MfYh9ut7NDqkzD6dkWk5tz_dI,13231
|
||||
sqlalchemy/sql/annotation.py,sha256=PslN1KQV9hN8Ji4k8I3-W-cDuRMCCLwMmJcg-n86Yy4,18830
|
||||
sqlalchemy/sql/base.py,sha256=LY4zzFtADcqGcvKPUuuC9rK-vucqmHn3zKklVivoZ-M,76110
|
||||
sqlalchemy/sql/cache_key.py,sha256=nEvUQ4yjtWWblrKjLLDd_b9i5zudgYhkOdJvI1U8Lvo,34725
|
||||
sqlalchemy/sql/coercions.py,sha256=wFZIVoYcmAoQYSHbv268cIzLfBVX_iI7d1evFSQFuL4,42069
|
||||
sqlalchemy/sql/compiler.py,sha256=GXd4pruoy7-fMTc8-4GSQyHCTqBMzpykn3WVvqxhkYM,282505
|
||||
sqlalchemy/sql/crud.py,sha256=YL1HIVrUkcduncUZ6F4gEBf5PlowguGkxMAlgMVlKgI,58183
|
||||
sqlalchemy/sql/ddl.py,sha256=cebT_-dEY79LvqM63rFN8uaMLZXtTvP7pT4yO_qWlXE,47014
|
||||
sqlalchemy/sql/default_comparator.py,sha256=lXmd8yAUzfyeP5w4vebrQG99oC0bTrmdGc0crBq1GKw,17259
|
||||
sqlalchemy/sql/dml.py,sha256=lt5FC6BbJNotE65U-fmvEovBxkADfKBnVcnkVYYQxUM,67431
|
||||
sqlalchemy/sql/elements.py,sha256=FudYJXww47JMyXnyZnX2foxsXITLLhFTQkFIWvPXf0s,182043
|
||||
sqlalchemy/sql/events.py,sha256=pG3jqJbPX18N9Amp47aXiQYMYD_HL_lOXHk-0m8m7Hw,18745
|
||||
sqlalchemy/sql/expression.py,sha256=T-AgCPp30tgKQYLKeSyqQg_VoJFE69m2yDTz6fn-u1E,7748
|
||||
sqlalchemy/sql/functions.py,sha256=9iazLxbvCMH35CzgtZaNJ0IUn8wz04w9DofY3jNfIHc,65817
|
||||
sqlalchemy/sql/lambdas.py,sha256=eKlhUhD8urNVvOm_1tUf8ESPIpo2qTAidKHJEarUhj8,50741
|
||||
sqlalchemy/sql/naming.py,sha256=ERVjqo6fBHBw2BwNgpbb5cvsCkq1jjdztczP9BKzVt8,7070
|
||||
sqlalchemy/sql/operators.py,sha256=CgijIxSVCh00XnqfhtK0sHD-gR5HlQt8LdJUZlPBu2E,78685
|
||||
sqlalchemy/sql/roles.py,sha256=8nO4y1hbP1cA8IzeOn6uPgNZNVILb3E-IMeJWOIScu8,7985
|
||||
sqlalchemy/sql/schema.py,sha256=bCxaa_Y_wZrkDauJX4Cr4Lr1vZaqNCoK3Dzd0ki_fsQ,235875
|
||||
sqlalchemy/sql/selectable.py,sha256=5jDioKY0V9fT731TtWHtUTKuzO6beUABx9l-fjh-ywo,243464
|
||||
sqlalchemy/sql/sqltypes.py,sha256=H5YpFPXEPVmDGrRLsLaOL3Whr7WsHstblIe_GRvA1MU,131296
|
||||
sqlalchemy/sql/traversals.py,sha256=dPHz1Kyyo_a5JcJiajR39iQejisGqefqUjYXqlBT7Vo,34688
|
||||
sqlalchemy/sql/type_api.py,sha256=0bHHe4o2333vj_hMbLfAMIZ0_Hjee00EdP9ItSRZZI8,86803
|
||||
sqlalchemy/sql/util.py,sha256=ftTiyNGeJK0MIRMqWMV7Xf8iZuiRGocoJRp3MIO3F3Y,49563
|
||||
sqlalchemy/sql/visitors.py,sha256=oudlabsf9qleuC78GFe_iflRSAD8H-HjaM7T8Frc538,37482
|
||||
sqlalchemy/testing/__init__.py,sha256=X7Td6ZZXYkZ_I58YPcHZnZ11RwwAmbnsC1ds3F6lOj8,3256
|
||||
sqlalchemy/testing/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertions.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertsql.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/asyncio.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/config.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/engines.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/entities.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/exclusions.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/pickleable.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/profiling.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/provision.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/requirements.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/schema.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/util.cpython-311.pyc,,
|
||||
sqlalchemy/testing/__pycache__/warnings.cpython-311.pyc,,
|
||||
sqlalchemy/testing/assertions.py,sha256=bBn2Ep89FF-WBmzh0VkvnJ9gNMKuqk8OXq7ALpUwar4,32428
|
||||
sqlalchemy/testing/assertsql.py,sha256=gj4YRBR9cjOtS1WgR3nsyIze1tmqctsNs1uCV8N2Q4w,17333
|
||||
sqlalchemy/testing/asyncio.py,sha256=xYuWjKFHzolBLgddy1ePI9l8KRRUOWpT-FWjhtV2Ei0,3965
|
||||
sqlalchemy/testing/config.py,sha256=jfFVUiAOm8im6SlqyAdZVSaA51kmADgfBDqrHnngH7c,12517
|
||||
sqlalchemy/testing/engines.py,sha256=8R7nbmLNUv2w7tiyVpiVI1s-57wpEs70UAgY-pkPX8k,13953
|
||||
sqlalchemy/testing/entities.py,sha256=Um-DFSz81p06DhTK899ZRUOZRw3FtUDeNMVHcIg3eLc,3471
|
||||
sqlalchemy/testing/exclusions.py,sha256=8kjsaFfjCvPlLsQLD_LIDwuqvVlIVbD5qTWBlKdtNkM,12895
|
||||
sqlalchemy/testing/fixtures/__init__.py,sha256=B1IFCzEVdCqhEvFrLmgxZ_Fr08jDus5FddSA-lnnAAU,1226
|
||||
sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/base.cpython-311.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-311.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/orm.cpython-311.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/sql.cpython-311.pyc,,
|
||||
sqlalchemy/testing/fixtures/base.py,sha256=S0ODuph0jA2Za4GN3NNhYVIqN9jAa3Q9Vd1N4O4rcTc,12622
|
||||
sqlalchemy/testing/fixtures/mypy.py,sha256=2H8QxvGvwsb_Z3alRtvCvfXeqGjOb8aemfoYxQiuGMc,12285
|
||||
sqlalchemy/testing/fixtures/orm.py,sha256=6JvQpIfmgmSTH3Hie4nhmUFfvH0pseujIFA9Lup2Dzw,6322
|
||||
sqlalchemy/testing/fixtures/sql.py,sha256=w0qyMBLibPqkhE5ZhJtHcJmIDJlwcPpnlq9v0WajrF8,16403
|
||||
sqlalchemy/testing/pickleable.py,sha256=uYLl557iNep6jSOVl0vK1GwaLHUKidALoPJc-QIrC08,2988
|
||||
sqlalchemy/testing/plugin/__init__.py,sha256=bbtVIt7LzVnUCcVxHWRH2owOQD067bQwwhyMf_whqHs,253
|
||||
sqlalchemy/testing/plugin/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-311.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-311.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-311.pyc,,
|
||||
sqlalchemy/testing/plugin/bootstrap.py,sha256=USn6pE-JcE5pSmnEd2wad3goKLx2hdJS3AUUFpXHm-I,1736
|
||||
sqlalchemy/testing/plugin/plugin_base.py,sha256=CgrNj2wj9KNALu9YfnGSaHX2fXfTtiim_cfx0CPVoy8,22357
|
||||
sqlalchemy/testing/plugin/pytestplugin.py,sha256=acuAWFec8QGzC_AWOhTsRRgB6dttkbNdoyGVb7WvTng,28524
|
||||
sqlalchemy/testing/profiling.py,sha256=o8_V3TpF_WytudMQQLm1UxlfNDrLCWxUvkH-Kd0unKU,10472
|
||||
sqlalchemy/testing/provision.py,sha256=l9KUBqR8_NLACZzE1f_twzjlkaVUzRa5tBwgJBmvqrY,15122
|
||||
sqlalchemy/testing/requirements.py,sha256=CuMwr9ZvQV-3va7jZ_dx8CW2TsjlhcQJ_DdNHvmFpJQ,54650
|
||||
sqlalchemy/testing/schema.py,sha256=z2Z5rm3iJ1-vgifUxwzxEjt1qu7QOyr3TeDnQdCHlWE,6737
|
||||
sqlalchemy/testing/suite/__init__.py,sha256=YvTEqUNHaBlgLgWDAWn79mQrUR4VBGUHtprywJlmDT8,741
|
||||
sqlalchemy/testing/suite/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_cte.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_insert.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_results.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_select.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_types.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-311.pyc,,
|
||||
sqlalchemy/testing/suite/test_cte.py,sha256=C_viXJKClFAm91rtPb42tiAA7gYJwKkqGYVJYap0cLM,6662
|
||||
sqlalchemy/testing/suite/test_ddl.py,sha256=k6D6RreLkDSSpRUM2hQz-_CA48qV2PYx_2LNyUSoZzE,12420
|
||||
sqlalchemy/testing/suite/test_deprecations.py,sha256=SKRFZDteBO1rw9-BQjDic5nh7fdyw2ypVOewR2pj7-Q,5490
|
||||
sqlalchemy/testing/suite/test_dialect.py,sha256=ftOWRXWOotB2_jMJJqwoH9f3X2ucc1HwwOiXp573GwM,23663
|
||||
sqlalchemy/testing/suite/test_insert.py,sha256=v3zrUZaGlke3cI4vabHg7xaI4gNqcHhtMPgYuf0mOxc,19454
|
||||
sqlalchemy/testing/suite/test_reflection.py,sha256=J--LmZcLez3B21WANzLj2zC_6gcKX-ycBQW2CMQ_azA,112873
|
||||
sqlalchemy/testing/suite/test_results.py,sha256=i2a_XiUJ5D9womKtJsveVUBUeJRpOOPrr1fJuen_qEY,17416
|
||||
sqlalchemy/testing/suite/test_rowcount.py,sha256=DCEGxorDcrT5JCLd3_SNQeZmxT6sKIcuKxX1r6vK4Mg,8158
|
||||
sqlalchemy/testing/suite/test_select.py,sha256=_HTef0mBmcvL2uS9z4G73AXZwXvjerQpl8wF1vqPt4I,63731
|
||||
sqlalchemy/testing/suite/test_sequence.py,sha256=sIqkfgVqPIgl4lm75EPdag9gK-rTHfUm3pWX-JijPy4,10240
|
||||
sqlalchemy/testing/suite/test_types.py,sha256=6RiN7L1g3b_3GUw8G_i39WB3M0W4tHNYTiwu57thzR0,69946
|
||||
sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=juF_KTK1nGrSlsL8z0Ky0rFSNkPGheLB3e0Kq3yRqss,6330
|
||||
sqlalchemy/testing/suite/test_update_delete.py,sha256=TnJI5U_ZEuu3bni4sH-S6CENxvSZwDgZL-FKSV45bAo,4133
|
||||
sqlalchemy/testing/util.py,sha256=rQqTjYvb9wQt1UfeZZHNuPG3aNxFyjrvVQUhtfZQYjc,15006
|
||||
sqlalchemy/testing/warnings.py,sha256=3EhbTlPe4gJnoydj-OKueNOOtGwIRF2kV4XvlFwFYOA,1598
|
||||
sqlalchemy/types.py,sha256=unCm_O8qKxU3LjLbqeqSNQSsK5k5R5POsyEx2gH6CF4,3244
|
||||
sqlalchemy/util/__init__.py,sha256=kE29cmjratY0xTq0vIQskMqFbKKmU69fLzer3TOAfYY,8472
|
||||
sqlalchemy/util/__pycache__/__init__.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/_collections.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/_has_cy.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/_py_collections.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/compat.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/concurrency.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/deprecations.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/langhelpers.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/preloaded.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/queue.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/tool_support.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/topological.cpython-311.pyc,,
|
||||
sqlalchemy/util/__pycache__/typing.cpython-311.pyc,,
|
||||
sqlalchemy/util/_collections.py,sha256=602PFadp7l1ZMJdqNTt7gkj-4-Xid0Ioo2xy4o6ICVI,20793
|
||||
sqlalchemy/util/_concurrency_py3k.py,sha256=8GB5fJVXvKp8BGyDBmFff3fTXQ71QUzkALiscX8qfYE,9458
|
||||
sqlalchemy/util/_has_cy.py,sha256=IHGc5hUFbXQuv1a1z2P8yVwz0yGbCYXyQM2qsdcBTyg,1287
|
||||
sqlalchemy/util/_py_collections.py,sha256=2PUqiKIsF8d-gNDAAqYI8WE6XPyRf1flRLkVsJeXuOo,17255
|
||||
sqlalchemy/util/compat.py,sha256=cJlRkbMD1DjV46dt1S9e4eboqDpKI5FotLYYVUjQ3J4,9061
|
||||
sqlalchemy/util/concurrency.py,sha256=zlmuK99p5cPpEPxBQYSDfLHP0Pbuw4iDeUzU49Pb1Ow,3412
|
||||
sqlalchemy/util/deprecations.py,sha256=AnHpDWHi7g2gv_QUTGStQTnr0J94lIF-3aFLOsv9yzg,12372
|
||||
sqlalchemy/util/langhelpers.py,sha256=6-bwfvTw6x1Ev8rsnHFHXYJOZVJvphrv85fQAGtAyn0,67308
|
||||
sqlalchemy/util/preloaded.py,sha256=78Sl7VjzTOPajbovvARxNeuZb-iYRpEvL5k8m5Bz4vQ,6054
|
||||
sqlalchemy/util/queue.py,sha256=4SbSbVamUECjCDpMPR035N1ooVHt9W5GjbqkxfZmH5k,10507
|
||||
sqlalchemy/util/tool_support.py,sha256=DuurikYgDUIIxk3gubUKl6rs-etXt3eeHaZ4ZkIyJXQ,6336
|
||||
sqlalchemy/util/topological.py,sha256=_NdtAghZjhZ4e2fwWHmn25erP5cvtGgOUMplsCa_VCE,3578
|
||||
sqlalchemy/util/typing.py,sha256=BTBK8Lb_4wm_1Jop0U98ULBk3a2PaN2wA1OegAhv1tA,18811
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue