# Prisma Web3 Python
<div align="center">
[](https://www.python.org/downloads/)
[](https://www.sqlalchemy.org/)
[](https://docs.python.org/3/library/asyncio.html)
[](LICENSE)
**异步 Web3 数据库 ORM** - 基于 SQLAlchemy 2.0 + AsyncIO 的高性能区块链数据访问层
[特性](#-特性) • [安装](#-安装) • [快速开始](#-快速开始) • [文档](#-文档) • [示例](#-示例) • [扩展](#-扩展)
</div>
---
## 📖 目录
- [简介](#-简介)
- [特性](#-特性)
- [架构](#-架构)
- [安装](#-安装)
- [快速开始](#-快速开始)
- [核心概念](#-核心概念)
- [详细使用](#-详细使用)
- [扩展开发](#-扩展开发)
- [API 参考](#-api-参考)
- [最佳实践](#-最佳实践)
- [常见问题](#-常见问题)
---
## 🎯 简介
**Prisma Web3 Python** 是一个专为 Web3 应用设计的异步数据库 ORM 层,提供:
- 🚀 **高性能异步操作** - 基于 AsyncIO + AsyncPG
- 🔄 **跨链支持** - 统一的数据模型处理多链资产
- 🎨 **简洁的 API** - Repository 模式,开箱即用
- 🔌 **完全可扩展** - 暴露所有底层组件,支持自定义
- 📊 **类型安全** - 完整的类型提示支持
- 🌐 **链名规范化** - 自动处理链名缩写和标准名转换
**适用场景**:
- Web3 数据分析平台
- Token 追踪和监控系统
- 链上信号聚合服务
- DeFi 数据仓库
- NFT 元数据管理
---
## ✨ 特性
### 核心功能
| 功能 | 说明 |
|------|------|
| **异步优先** | 全异步 API,支持高并发操作 |
| **跨链设计** | 单表设计存储跨链 Token,支持多链地址映射 |
| **链名智能化** | 自动规范化链名(`sol` ↔ `solana`,`bsc` ↔ `binance-smart-chain`) |
| **Repository 模式** | 预构建的数据访问层,包含常用查询方法 |
| **灵活查询** | 支持符号、名称、别名、模糊搜索 |
| **批量操作** | 高效的批量插入和更新 |
| **完整扩展性** | 暴露 Models、Repositories、Session 等所有组件 |
### 数据模型
#### Token(代币)
- 跨链 Token 信息存储
- 支持 platforms 字段存储多链地址
- 自动主链选择(按优先级)
- 社交链接、分类、别名支持
#### Signal(信号)
- Token 信号追踪
- 来源、类型、频次统计
- 时间序列分析
#### PreSignal(预信号)
- 早期信号捕获
- 多维度评分(频道呼声、多信号、KOL讨论)
- 状态管理(开放/已转换/已关闭)
#### CryptoNews(加密新闻)
- 多源新闻聚合(TechFlow、ChainCatcher 等)
- 智能实体识别(关联的代币、股票、实体)
- JSONB 高效搜索(支持复杂查询)
- 趋势分析(热门货币、热门实体)
---
## 🏗️ 架构
```
┌─────────────────────────────────────────────────────┐
│ Your Application │
│ (FastAPI / Flask / Django / Custom) │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Prisma Web3 Python Package │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Repositories │◄───│ Models │ │
│ │ - Token │ │ - Token │ │
│ │ - Signal │ │ - Signal │ │
│ │ - PreSignal │ │ - PreSignal│ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Database │ │
│ │ (Session Mgmt) │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────┐
│ PostgreSQL Database │
│ (AsyncPG Driver) │
└──────────────────────────┘
```
---
## 📦 安装
### 要求
- Python 3.8+
- PostgreSQL 12+
- AsyncPG 驱动
### 使用 pip 安装
\`\`\`bash
# 基础安装
pip install prisma-web3-py
# 从源码安装(开发版)
git clone https://github.com/your-org/prisma-web3.git
cd prisma-web3/python
pip install -e .
\`\`\`
### 数据库设置
1. **创建数据库**:
\`\`\`bash
psql -U postgres
CREATE DATABASE your_database;
\`\`\`
2. **运行迁移**(使用 Prisma):
\`\`\`bash
cd ../ # 回到项目根目录
npx prisma migrate dev
\`\`\`
3. **配置环境变量**:
\`\`\`bash
# .env
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/your_database
\`\`\`
---
## 🚀 快速开始
### 1. 初始化数据库连接
\`\`\`python
import asyncio
from prisma_web3_py import init_db, close_db, get_db
async def main():
# 初始化数据库连接池
await init_db()
try:
# 你的业务逻辑
async with get_db() as session:
# 使用 session 进行数据库操作
pass
finally:
# 关闭连接池
await close_db()
if __name__ == "__main__":
asyncio.run(main())
\`\`\`
### 2. 使用 Repository 查询
\`\`\`python
from prisma_web3_py import get_db, TokenRepository
async def query_tokens():
repo = TokenRepository()
async with get_db() as session:
# 获取 Token(支持链名缩写!)
token = await repo.get_by_address(
session,
chain='sol', # 自动转换为 'solana'
token_address='oobQ3oX6ubRYMNMahG7VSCe8Z73uaQbAWFn6f22XTgo'
)
print(f"Token: {token.symbol} - {token.name}")
print(f"Chain: {token.chain}") # 输出: solana
# 搜索 Tokens
tokens = await repo.search_tokens(session, "BTC", limit=10)
for t in tokens:
print(f"- {t.symbol}: {t.name}")
\`\`\`
### 3. 插入数据
\`\`\`python
from prisma_web3_py import get_db, TokenRepository
async def insert_token():
repo = TokenRepository()
async with get_db() as session:
token_data = {
"chain": "eth", # 使用缩写
"token_address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"symbol": "UNI",
"name": "Uniswap",
"coingecko_id": "uniswap",
"decimals": 18,
"platforms": {
"ethereum": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"polygon-pos": "0xb33eaad8d922b1083446dc23f610c2567fb5180f"
}
}
token_id = await repo.upsert_token(session, token_data)
await session.commit()
print(f"Token saved with ID: {token_id}")
\`\`\`
### 4. 创建数据(重要!)
**⚠️ 注意**: 使用 Repository 的 `create()` 方法,而不是直接创建 Model 对象!
\`\`\`python
from prisma_web3_py import get_db, PreSignalRepository
async def create_pre_signal():
repo = PreSignalRepository()
async with get_db() as session:
# ✅ 正确:使用 repository.create(),自动处理 chain 标准化
pre_signal = await repo.create(
session,
source="jin_vip",
chain="sol", # 自动转换为 'solana'
token_address="oobQ3oX6ubRYMNMahG7VSCe8Z73uaQbAWFn6f22XTgo",
signal_type="jin_vip",
channel_calls=5,
multi_signals=3,
kol_discussions=2,
token_narrative="Some description"
)
if pre_signal:
await session.commit()
print(f"✅ Created PreSignal ID: {pre_signal.id}")
else:
print("❌ Failed to create PreSignal")
# ❌ 错误示例:不要直接创建 Model 对象!
async def wrong_way():
from prisma_web3_py import PreSignal
async with get_db() as session:
# ❌ 这样会导致外键错误,因为 chain 没有标准化!
pre_signal = PreSignal(
source="jin_vip",
chain="sol", # 不会自动转换,导致外键约束失败
token_address="...",
signal_type="jin_vip"
)
session.add(pre_signal) # ❌ 会报错!
\`\`\`
**为什么必须使用 Repository?**
1. **自动链名标准化**: Repository 会自动将 `'sol'` 转换为 `'solana'`
2. **外键约束**: Token 表存储的是标准名,直接使用缩写会违反外键约束
3. **数据验证**: Repository 提供额外的数据验证和错误处理
4. **一致性**: 确保所有数据以统一格式存储
### 5. 使用 CryptoNews(加密新闻)
\`\`\`python
from datetime import datetime
from prisma_web3_py import get_db, CryptoNewsRepository
async def crypto_news_example():
repo = CryptoNewsRepository()
async with get_db() as session:
# 1. 导入新闻数据(从 API)
api_data = {
"title": "OKX 将上线 SEI (Sei),2Z (DoubleZero)现货交易",
"category": 1,
"source": "TechFlow",
"content": "11 月 14 日,据官方公告...",
"matchedCurrencies": [{"name": "SEI"}, {"name": "2Z"}],
"entityList": ["OKX", "SEI", "2Z"],
"createTime": "1763089364248" # 毫秒时间戳
}
# 转换时间戳并创建新闻
news_time = datetime.fromtimestamp(int(api_data["createTime"]) / 1000)
news = await repo.create_news(
session,
title=api_data["title"],
category=api_data["category"],
source=api_data["source"],
content=api_data["content"],
matched_currencies=api_data.get("matchedCurrencies", []),
entity_list=api_data.get("entityList", []),
news_created_at=news_time
)
await session.commit()
# 2. 按加密货币搜索新闻
btc_news = await repo.search_by_currency(session, "BTC", hours=24)
print(f"关于 BTC 的新闻: {len(btc_news)} 条")
# 3. 按实体搜索新闻
okx_news = await repo.search_by_entity(session, "OKX", hours=24)
print(f"提到 OKX 的新闻: {len(okx_news)} 条")
# 4. 获取热门货币
trending = await repo.get_trending_currencies(session, hours=24, limit=10)
print("热门货币:")
for item in trending[:5]:
print(f" {item['currency']}: {item['mentions']} 次提及")
# 5. 获取热门实体
trending_entities = await repo.get_trending_entities(session, hours=24)
print("热门实体:")
for item in trending_entities[:5]:
print(f" {item['entity']}: {item['mentions']} 次提及")
# 6. 搜索标签
defi_news = await repo.search_by_tag(session, "defi", hours=24)
print(f"DeFi 相关新闻: {len(defi_news)} 条")
\`\`\`
### 6. 使用 Models 直接查询
\`\`\`python
from prisma_web3_py import get_db, Token, Signal
from sqlalchemy import select, func
async def custom_query():
async with get_db() as session:
# 自定义复杂查询
stmt = (
select(Token, func.count(Signal.id).label('signal_count'))
.join(Signal, (Token.chain == Signal.chain) &
(Token.token_address == Signal.token_address))
.group_by(Token.id)
.order_by(func.count(Signal.id).desc())
.limit(10)
)
result = await session.execute(stmt)
for token, count in result:
print(f"{token.symbol}: {count} signals")
\`\`\`
---
## 💡 核心概念
### 1. Repository Pattern(仓储模式)
Repository 是数据访问层的抽象,隐藏了 SQL 查询细节。
所有 Repository 都继承自 `BaseRepository`,提供基础 CRUD 方法。
### 2. 链名规范化
所有 Repository 自动处理链名转换:
\`\`\`python
# 这些都可以工作
await repo.get_by_address(session, "sol", "address") # 缩写
await repo.get_by_address(session, "eth", "address") # 缩写
await repo.get_by_address(session, "solana", "address") # 标准名
# Repository 会自动转换为 CoinGecko 标准名存入数据库
\`\`\`
支持的链:Ethereum (`eth`), BSC (`bsc`), Solana (`sol`), Polygon (`poly`), Arbitrum (`arb`), Base (`base`) 等 18+ 条链。
### 3. 跨链 Token 设计
Token 表采用单表设计存储跨链资产:
\`\`\`python
{
"chain": "ethereum", # 主链(优先级最高)
"token_address": "0x...", # 主链地址
"symbol": "UNI",
"platforms": { # 跨链地址映射 (JSONB)
"ethereum": "0x...",
"polygon-pos": "0x...",
"arbitrum-one": "0x..."
}
}
\`\`\`
### 4. 异步 Context Manager
使用 `get_db()` 自动管理 Session 生命周期:
\`\`\`python
async with get_db() as session:
# session 自动创建
result = await repo.get_all(session)
await session.commit()
# session 自动关闭
\`\`\`
---
## 📚 详细使用
### 使用 get_db() 和 Repository 的完整指南
#### 基础模式:使用 Context Manager
\`\`\`python
from prisma_web3_py import get_db, TokenRepository
async def basic_usage():
# 1. 创建 repository 实例
repo = TokenRepository()
# 2. 使用 get_db() 获取 session
async with get_db() as session:
# 3. 执行数据库操作
token = await repo.get_by_address(
session,
chain='sol', # 自动标准化
token_address='xxx'
)
# 4. 提交事务
await session.commit()
# session 自动关闭
\`\`\`
#### 完整的增删改查示例
\`\`\`python
from prisma_web3_py import (
get_db,
TokenRepository,
SignalRepository,
PreSignalRepository
)
async def crud_examples():
token_repo = TokenRepository()
signal_repo = SignalRepository()
pre_signal_repo = PreSignalRepository()
async with get_db() as session:
# ========== CREATE ==========
# 方式1: 使用 repository.create()(推荐)
pre_signal = await pre_signal_repo.create(
session,
source="source1",
chain="sol", # ✅ 自动转换为 'solana'
token_address="xxx",
signal_type="type1",
channel_calls=5
)
# 方式2: 使用专用方法
signal = await signal_repo.upsert_signal(
session,
chain="eth", # ✅ 自动转换为 'ethereum'
token_address="0x123",
source="source1",
signal_type="kol"
)
# ========== READ ==========
# 单个查询
token = await token_repo.get_by_address(
session,
chain='bsc', # ✅ 自动转换为 'binance-smart-chain'
token_address='0x456'
)
# 批量查询
recent_tokens = await token_repo.get_recent_tokens(
session,
chain='sol',
limit=100
)
# 搜索
search_results = await token_repo.search_tokens(
session,
search_term='BTC',
limit=20
)
# ========== UPDATE ==========
# 使用 BaseRepository 的 update_by_id
success = await token_repo.update_by_id(
session,
id=token.id,
symbol='NEW_SYMBOL'
)
# 或者使用 upsert
token_id = await token_repo.upsert_token(
session,
{
'chain': 'eth',
'token_address': '0x789',
'symbol': 'UNI',
'name': 'Uniswap'
}
)
# ========== DELETE ==========
success = await token_repo.delete_by_id(session, id=123)
# 提交所有更改
await session.commit()
\`\`\`
#### 处理第三方数据的正确方式
当你从外部源(API、消息队列等)接收数据时:
\`\`\`python
async def handle_external_data(data: dict):
"""
处理外部数据的正确方式
Args:
data: 来自第三方的数据,例如:
{
"source": "jin_vip",
"chain": "sol", # 可能是缩写
"token_address": "xxx",
"signal_type": "jin_vip",
"signals": {
"channel_calls": 5,
"multi_signals": 3,
"kol_discussions": 2
},
"description": "Token narrative..."
}
"""
pre_signal_repo = PreSignalRepository()
# 准备数据
pre_signal_data = {
"source": data.get("source"),
"chain": data.get("chain"), # 保持原样,repository 会处理
"token_address": data.get("token_address"),
"signal_type": data.get("signal_type"),
}
# 添加 signals 数据
signals = data.get("signals", {})
if signals:
pre_signal_data.update({
"channel_calls": signals.get("channel_calls", 0),
"multi_signals": signals.get("multi_signals", 0),
"kol_discussions": signals.get("kol_discussions", 0),
})
# 添加代币叙事
if data.get("description"):
pre_signal_data["token_narrative"] = data["description"]
# ✅ 正确方式:使用 repository.create()
async with get_db() as session:
pre_signal = await pre_signal_repo.create(
session,
**{k: v for k, v in pre_signal_data.items() if v is not None}
)
if pre_signal:
await session.commit()
print(f"✅ Created PreSignal ID: {pre_signal.id}")
return pre_signal
else:
print("❌ Failed to create PreSignal")
return None
# ❌ 错误方式:直接创建 Model 对象
# from prisma_web3_py import PreSignal
# async with get_db() as session:
# pre_signal = PreSignal(**pre_signal_data) # ❌ chain 不会标准化!
# session.add(pre_signal)
# await session.commit() # ❌ 外键约束错误!
\`\`\`
#### 批量操作
\`\`\`python
async def bulk_operations():
repo = TokenRepository()
async with get_db() as session:
# 批量创建
tokens_data = [
{"chain": "eth", "token_address": "0x1", "symbol": "TKN1"},
{"chain": "bsc", "token_address": "0x2", "symbol": "TKN2"},
{"chain": "sol", "token_address": "xxx", "symbol": "TKN3"},
]
for token_data in tokens_data:
# 使用 repository.create() 确保 chain 标准化
token = await repo.create(session, **token_data)
if not token:
print(f"Failed to create {token_data}")
# 一次性提交所有更改
await session.commit()
\`\`\`
#### 错误处理
\`\`\`python
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
async def error_handling():
repo = PreSignalRepository()
async with get_db() as session:
try:
pre_signal = await repo.create(
session,
source="source1",
chain="sol",
token_address="xxx",
signal_type="type1"
)
await session.commit()
return pre_signal
except IntegrityError as e:
# 外键约束、唯一约束违反
await session.rollback()
print(f"Integrity error: {e}")
raise
except SQLAlchemyError as e:
# 其他数据库错误
await session.rollback()
print(f"Database error: {e}")
raise
except Exception as e:
# 其他错误
await session.rollback()
print(f"Unexpected error: {e}")
raise
\`\`\`
### 主要操作概览
- ✅ **Token 查询、创建、更新** - 使用 `TokenRepository`
- ✅ **Signal 管理** - 使用 `SignalRepository`
- ✅ **PreSignal 处理** - 使用 `PreSignalRepository`
- ✅ **自定义查询** - 直接使用 Models + SQLAlchemy
- ✅ **批量导入** - 使用 `TokenImporter`
详细 API 文档请参考 [API 参考](#-api-参考) 部分。
---
## 🔌 扩展开发
Prisma Web3 Python 完全可扩展。详细指南请参考 [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md)。
### 快速示例
#### 1. 继承 BaseRepository
\`\`\`python
from prisma_web3_py import BaseRepository, Token
class MyTokenRepository(BaseRepository[Token]):
async def get_high_value_tokens(self, session, min_supply: float):
# 自定义查询
pass
\`\`\`
#### 2. 使用 Models 直接查询
\`\`\`python
from prisma_web3_py import get_db, Token
from sqlalchemy import select
async with get_db() as session:
stmt = select(Token).where(Token.symbol == 'BTC')
result = await session.execute(stmt)
\`\`\`
#### 3. 扩展现有 Repository
\`\`\`python
from prisma_web3_py import TokenRepository
class ExtendedTokenRepository(TokenRepository):
async def new_feature(self, session):
# 添加新方法
pass
\`\`\`
---
## 📖 API 参考
### 核心组件
\`\`\`python
from prisma_web3_py import (
# Core
Base, get_db, init_db, close_db, AsyncSessionLocal,
# Models
Token, Signal, PreSignal, SignalStatus, CryptoNews,
# Repositories
BaseRepository, TokenRepository, SignalRepository, PreSignalRepository,
CryptoNewsRepository,
# Utils
TokenImporter, ChainConfig
)
\`\`\`
### TokenRepository 主要方法
- `get_by_address(session, chain, token_address)` - 按链和地址查询
- `search_tokens(session, search_term, chain, limit)` - 搜索
- `search_by_symbol(session, symbol, exact)` - 按符号搜索
- `search_by_name(session, name, exact)` - 按名称搜索
- `search_by_alias(session, alias)` - 按别名搜索(使用 JSONB @> 操作符)
- `fuzzy_search(session, text, threshold, limit)` - 模糊搜索(支持 pg_trgm)
- `upsert_token(session, token_data)` - 插入或更新
- `get_recent_tokens(session, chain, limit)` - 最近创建
- `get_recently_updated_tokens(session, hours, chain, limit)` - 最近更新
### CryptoNewsRepository 主要方法
**创建和基础查询**:
- `create_news(session, title, category, source, content, ...)` - 创建新闻
- `get_recent_news(session, hours, source, sector, limit)` - 获取最近新闻
- `get_news_by_source(session, source, hours, limit)` - 按来源查询
- `get_news_by_sector(session, sector, hours, limit)` - 按行业查询
**JSONB 高级查询**(使用 PostgreSQL @> 操作符):
- `search_by_currency(session, currency_name, hours, limit)` - 按加密货币搜索
- `search_by_entity(session, entity_name, hours, limit)` - 按实体搜索
- `search_by_tag(session, tag, hours, limit)` - 按标签搜索
**趋势分析**(使用 jsonb_array_elements):
- `get_trending_currencies(session, hours, limit)` - 获取热门货币
- `get_trending_entities(session, hours, limit)` - 获取热门实体
**其他**:
- `search_news(session, search_term, search_in_content, limit)` - 关键词搜索
- `get_news_statistics(session, hours)` - 统计信息
完整 API 请查看源码注释。
---
## 🎯 最佳实践
### 1. 始终使用 Repository 创建数据
✅ **推荐**:
\`\`\`python
# 使用 repository.create()
repo = PreSignalRepository()
async with get_db() as session:
result = await repo.create(session, chain='sol', ...)
await session.commit()
\`\`\`
❌ **避免**:
\`\`\`python
# 直接创建 Model 对象
from prisma_web3_py import PreSignal
pre_signal = PreSignal(chain='sol', ...) # 不会标准化 chain
session.add(pre_signal)
\`\`\`
### 2. 使用 Context Manager
✅ **推荐**:
\`\`\`python
async with get_db() as session:
result = await repo.get_all(session)
await session.commit()
# session 自动关闭,连接归还到连接池
\`\`\`
❌ **避免**:
\`\`\`python
session = AsyncSessionLocal() # 手动管理
result = await repo.get_all(session)
await session.close() # 容易忘记
\`\`\`
### 3. 正确的错误处理
✅ **推荐**:
\`\`\`python
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
async with get_db() as session:
try:
result = await repo.create(session, **data)
await session.commit()
return result
except IntegrityError as e:
# 外键、唯一约束错误
await session.rollback()
logger.error(f"Integrity error: {e}")
raise
except SQLAlchemyError as e:
# 其他数据库错误
await session.rollback()
logger.error(f"Database error: {e}")
raise
\`\`\`
### 4. 使用链名缩写
✅ **推荐**:
\`\`\`python
# 使用缩写,更简洁
await repo.get_by_address(session, 'sol', 'address')
await repo.get_by_address(session, 'eth', 'address')
await repo.get_by_address(session, 'bsc', 'address')
\`\`\`
✅ **也可以**:
\`\`\`python
# 使用标准名,更明确
await repo.get_by_address(session, 'solana', 'address')
await repo.get_by_address(session, 'ethereum', 'address')
\`\`\`
### 5. 处理外部数据
当接收外部数据(API、消息队列)时:
✅ **推荐**:
\`\`\`python
async def handle_external_data(data: dict):
repo = PreSignalRepository()
async with get_db() as session:
# repository 会自动标准化 chain
result = await repo.create(session, **data)
await session.commit()
return result
\`\`\`
❌ **避免**:
\`\`\`python
from prisma_web3_py import PreSignal
async with get_db() as session:
# 外部 data['chain'] 可能是 'sol',不会被标准化
obj = PreSignal(**data)
session.add(obj) # ❌ 可能导致外键错误
\`\`\`
### 6. 批量操作的事务处理
✅ **推荐**:
\`\`\`python
async with get_db() as session:
for item in items:
await repo.create(session, **item)
# 一次性提交所有操作
await session.commit()
\`\`\`
❌ **避免**:
\`\`\`python
# 每个操作都单独提交
for item in items:
async with get_db() as session:
await repo.create(session, **item)
await session.commit() # 性能低下
\`\`\`
---
## ❓ 常见问题
### Q1: 为什么会出现外键约束错误?
**A**: 最常见的原因是直接创建 Model 对象而不是使用 Repository:
\`\`\`python
# ❌ 错误:直接创建对象
from prisma_web3_py import PreSignal
pre_signal = PreSignal(chain='sol', ...) # chain 不会标准化
session.add(pre_signal) # ❌ 外键错误!
# ✅ 正确:使用 repository
from prisma_web3_py import PreSignalRepository
repo = PreSignalRepository()
pre_signal = await repo.create(session, chain='sol', ...) # ✅ 自动标准化
\`\`\`
**解决方案**: 始终使用 Repository 的 `create()` 方法或专用方法来创建数据。
### Q2: 如何处理链名?
**A**: 所有 Repository 都自动规范化链名。你可以使用缩写(`sol`, `eth`, `bsc`)或标准名(`solana`, `ethereum`, `binance-smart-chain`),数据库会统一存储为 CoinGecko 标准名。
**链名映射表**:
- `sol` → `solana`
- `eth` → `ethereum`
- `bsc` → `binance-smart-chain`
- `poly` → `polygon-pos`
- `arb` → `arbitrum-one`
- `base` → `base`
- 等 18+ 条链...
### Q3: 可以直接使用 SQLAlchemy Model 吗?
**A**: **查询可以,创建不建议**。
\`\`\`python
# ✅ 查询:可以直接使用 Model
from prisma_web3_py import Token
from sqlalchemy import select
stmt = select(Token).where(Token.symbol == 'BTC')
result = await session.execute(stmt)
# ❌ 创建:不要直接创建 Model
token = Token(chain='sol', ...) # 不会标准化
session.add(token) # 可能导致错误
# ✅ 创建:使用 Repository
repo = TokenRepository()
token = await repo.create(session, chain='sol', ...) # 自动标准化
\`\`\`
### Q4: 如何执行自定义查询?
**A**: 三种方式:
1. 直接使用 Models + SQLAlchemy(仅查询)
2. 继承 BaseRepository(添加新方法)
3. 扩展现有 Repository(扩展功能)
详见 [扩展开发](#-扩展开发) 或 [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md)。
### Q5: 支持哪些数据库?
**A**: 目前只支持 **PostgreSQL**(使用 AsyncPG 驱动)。
### Q6: 处理外部数据时应该注意什么?
**A**: 外部数据(API、消息队列等)可能包含链名缩写,必须通过 Repository 处理:
\`\`\`python
# 外部数据
data = {"chain": "sol", "token_address": "xxx", ...}
# ✅ 正确:使用 repository.create()
repo = PreSignalRepository()
async with get_db() as session:
result = await repo.create(session, **data)
await session.commit()
\`\`\`
详见 [详细使用](#-详细使用) 中的"处理第三方数据的正确方式"。
---
## 📚 文档
- **扩展指南**: [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md) - 如何扩展模块
- **架构文档**: [ARCHITECTURE.md](ARCHITECTURE.md) - 系统架构说明
- **导入指南**: [IMPORT_GUIDE.md](IMPORT_GUIDE.md) - Token 数据导入
---
## 🛠️ 开发工具
### 测试
\`\`\`bash
# 运行所有测试
python scripts/run_all_tests.py
# 运行特定测试
python scripts/test_token.py
python scripts/test_signal.py
python scripts/test_pre_signal.py
\`\`\`
### 数据导入
\`\`\`bash
# 导入 token 数据
python scripts/import_token_recognition_data.py
\`\`\`
### 验证
\`\`\`bash
# 验证数据一致性
python scripts/verify_consistency.py
# 测试数据库连接
python scripts/test_connection.py
\`\`\`
---
## 📝 更新日志
### v0.1.8 (最新)
- ✨ 完全暴露 Models、Repositories、Session 等组件
- ✨ 新增扩展指南(EXTENSION_GUIDE.md)
- 🐛 修复外键约束问题(链名规范化)
- 📚 新增详细 README 文档
### v0.1.6
- ✨ 新增链名自动规范化功能
- ✨ TokenRepository 新增多种搜索方法
- ♻️ 移除 TokenRecognition 模块
---
## 📄 许可证
本项目采用 [MIT](LICENSE) 许可证。
---
## 🤝 贡献指南
欢迎贡献!请遵循以下步骤:
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
---
<div align="center">
**如果这个项目对你有帮助,请给我们一个 ⭐️!**
Made with ❤️ by the Prisma Web3 Team
</div>
Raw data
{
"_id": null,
"home_page": "https://github.com/your-org/prisma-web3",
"name": "prisma-web3-py",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "database, orm, sqlalchemy, async, web3, prisma",
"author": "SmallCat",
"author_email": "SmallCat <your-email@example.com>",
"download_url": "https://files.pythonhosted.org/packages/7c/7f/f506b80fa1072d6cc196c4a4800fa73f195fdf33cf6427528c65e9580d42/prisma_web3_py-0.2.3.tar.gz",
"platform": null,
"description": "# Prisma Web3 Python\n\n<div align=\"center\">\n\n[](https://www.python.org/downloads/)\n[](https://www.sqlalchemy.org/)\n[](https://docs.python.org/3/library/asyncio.html)\n[](LICENSE)\n\n**\u5f02\u6b65 Web3 \u6570\u636e\u5e93 ORM** - \u57fa\u4e8e SQLAlchemy 2.0 + AsyncIO \u7684\u9ad8\u6027\u80fd\u533a\u5757\u94fe\u6570\u636e\u8bbf\u95ee\u5c42\n\n[\u7279\u6027](#-\u7279\u6027) \u2022 [\u5b89\u88c5](#-\u5b89\u88c5) \u2022 [\u5feb\u901f\u5f00\u59cb](#-\u5feb\u901f\u5f00\u59cb) \u2022 [\u6587\u6863](#-\u6587\u6863) \u2022 [\u793a\u4f8b](#-\u793a\u4f8b) \u2022 [\u6269\u5c55](#-\u6269\u5c55)\n\n</div>\n\n---\n\n## \ud83d\udcd6 \u76ee\u5f55\n\n- [\u7b80\u4ecb](#-\u7b80\u4ecb)\n- [\u7279\u6027](#-\u7279\u6027)\n- [\u67b6\u6784](#-\u67b6\u6784) \n- [\u5b89\u88c5](#-\u5b89\u88c5)\n- [\u5feb\u901f\u5f00\u59cb](#-\u5feb\u901f\u5f00\u59cb)\n- [\u6838\u5fc3\u6982\u5ff5](#-\u6838\u5fc3\u6982\u5ff5)\n- [\u8be6\u7ec6\u4f7f\u7528](#-\u8be6\u7ec6\u4f7f\u7528)\n- [\u6269\u5c55\u5f00\u53d1](#-\u6269\u5c55\u5f00\u53d1)\n- [API \u53c2\u8003](#-api-\u53c2\u8003)\n- [\u6700\u4f73\u5b9e\u8df5](#-\u6700\u4f73\u5b9e\u8df5)\n- [\u5e38\u89c1\u95ee\u9898](#-\u5e38\u89c1\u95ee\u9898)\n\n---\n\n## \ud83c\udfaf \u7b80\u4ecb\n\n**Prisma Web3 Python** \u662f\u4e00\u4e2a\u4e13\u4e3a Web3 \u5e94\u7528\u8bbe\u8ba1\u7684\u5f02\u6b65\u6570\u636e\u5e93 ORM \u5c42\uff0c\u63d0\u4f9b\uff1a\n\n- \ud83d\ude80 **\u9ad8\u6027\u80fd\u5f02\u6b65\u64cd\u4f5c** - \u57fa\u4e8e AsyncIO + AsyncPG\n- \ud83d\udd04 **\u8de8\u94fe\u652f\u6301** - \u7edf\u4e00\u7684\u6570\u636e\u6a21\u578b\u5904\u7406\u591a\u94fe\u8d44\u4ea7\n- \ud83c\udfa8 **\u7b80\u6d01\u7684 API** - Repository \u6a21\u5f0f\uff0c\u5f00\u7bb1\u5373\u7528\n- \ud83d\udd0c **\u5b8c\u5168\u53ef\u6269\u5c55** - \u66b4\u9732\u6240\u6709\u5e95\u5c42\u7ec4\u4ef6\uff0c\u652f\u6301\u81ea\u5b9a\u4e49\n- \ud83d\udcca **\u7c7b\u578b\u5b89\u5168** - \u5b8c\u6574\u7684\u7c7b\u578b\u63d0\u793a\u652f\u6301\n- \ud83c\udf10 **\u94fe\u540d\u89c4\u8303\u5316** - \u81ea\u52a8\u5904\u7406\u94fe\u540d\u7f29\u5199\u548c\u6807\u51c6\u540d\u8f6c\u6362\n\n**\u9002\u7528\u573a\u666f**\uff1a\n- Web3 \u6570\u636e\u5206\u6790\u5e73\u53f0\n- Token \u8ffd\u8e2a\u548c\u76d1\u63a7\u7cfb\u7edf\n- \u94fe\u4e0a\u4fe1\u53f7\u805a\u5408\u670d\u52a1\n- DeFi \u6570\u636e\u4ed3\u5e93\n- NFT \u5143\u6570\u636e\u7ba1\u7406\n\n---\n\n## \u2728 \u7279\u6027\n\n### \u6838\u5fc3\u529f\u80fd\n\n| \u529f\u80fd | \u8bf4\u660e |\n|------|------|\n| **\u5f02\u6b65\u4f18\u5148** | \u5168\u5f02\u6b65 API\uff0c\u652f\u6301\u9ad8\u5e76\u53d1\u64cd\u4f5c |\n| **\u8de8\u94fe\u8bbe\u8ba1** | \u5355\u8868\u8bbe\u8ba1\u5b58\u50a8\u8de8\u94fe Token\uff0c\u652f\u6301\u591a\u94fe\u5730\u5740\u6620\u5c04 |\n| **\u94fe\u540d\u667a\u80fd\u5316** | \u81ea\u52a8\u89c4\u8303\u5316\u94fe\u540d\uff08`sol` \u2194 `solana`\uff0c`bsc` \u2194 `binance-smart-chain`\uff09 |\n| **Repository \u6a21\u5f0f** | \u9884\u6784\u5efa\u7684\u6570\u636e\u8bbf\u95ee\u5c42\uff0c\u5305\u542b\u5e38\u7528\u67e5\u8be2\u65b9\u6cd5 |\n| **\u7075\u6d3b\u67e5\u8be2** | \u652f\u6301\u7b26\u53f7\u3001\u540d\u79f0\u3001\u522b\u540d\u3001\u6a21\u7cca\u641c\u7d22 |\n| **\u6279\u91cf\u64cd\u4f5c** | \u9ad8\u6548\u7684\u6279\u91cf\u63d2\u5165\u548c\u66f4\u65b0 |\n| **\u5b8c\u6574\u6269\u5c55\u6027** | \u66b4\u9732 Models\u3001Repositories\u3001Session \u7b49\u6240\u6709\u7ec4\u4ef6 |\n\n### \u6570\u636e\u6a21\u578b\n\n#### Token\uff08\u4ee3\u5e01\uff09\n- \u8de8\u94fe Token \u4fe1\u606f\u5b58\u50a8\n- \u652f\u6301 platforms \u5b57\u6bb5\u5b58\u50a8\u591a\u94fe\u5730\u5740\n- \u81ea\u52a8\u4e3b\u94fe\u9009\u62e9\uff08\u6309\u4f18\u5148\u7ea7\uff09\n- \u793e\u4ea4\u94fe\u63a5\u3001\u5206\u7c7b\u3001\u522b\u540d\u652f\u6301\n\n#### Signal\uff08\u4fe1\u53f7\uff09\n- Token \u4fe1\u53f7\u8ffd\u8e2a\n- \u6765\u6e90\u3001\u7c7b\u578b\u3001\u9891\u6b21\u7edf\u8ba1\n- \u65f6\u95f4\u5e8f\u5217\u5206\u6790\n\n#### PreSignal\uff08\u9884\u4fe1\u53f7\uff09\n- \u65e9\u671f\u4fe1\u53f7\u6355\u83b7\n- \u591a\u7ef4\u5ea6\u8bc4\u5206\uff08\u9891\u9053\u547c\u58f0\u3001\u591a\u4fe1\u53f7\u3001KOL\u8ba8\u8bba\uff09\n- \u72b6\u6001\u7ba1\u7406\uff08\u5f00\u653e/\u5df2\u8f6c\u6362/\u5df2\u5173\u95ed\uff09\n\n#### CryptoNews\uff08\u52a0\u5bc6\u65b0\u95fb\uff09\n- \u591a\u6e90\u65b0\u95fb\u805a\u5408\uff08TechFlow\u3001ChainCatcher \u7b49\uff09\n- \u667a\u80fd\u5b9e\u4f53\u8bc6\u522b\uff08\u5173\u8054\u7684\u4ee3\u5e01\u3001\u80a1\u7968\u3001\u5b9e\u4f53\uff09\n- JSONB \u9ad8\u6548\u641c\u7d22\uff08\u652f\u6301\u590d\u6742\u67e5\u8be2\uff09\n- \u8d8b\u52bf\u5206\u6790\uff08\u70ed\u95e8\u8d27\u5e01\u3001\u70ed\u95e8\u5b9e\u4f53\uff09\n\n---\n\n## \ud83c\udfd7\ufe0f \u67b6\u6784\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Your Application \u2502\n\u2502 (FastAPI / Flask / Django / Custom) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Prisma Web3 Python Package \u2502\n\u2502 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 Repositories \u2502\u25c4\u2500\u2500\u2500\u2502 Models \u2502 \u2502\n\u2502 \u2502 - Token \u2502 \u2502 - Token \u2502 \u2502\n\u2502 \u2502 - Signal \u2502 \u2502 - Signal \u2502 \u2502\n\u2502 \u2502 - PreSignal \u2502 \u2502 - PreSignal\u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u2502 \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u25bc \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 Database \u2502 \u2502\n\u2502 \u2502 (Session Mgmt) \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25bc\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 PostgreSQL Database \u2502\n \u2502 (AsyncPG Driver) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n---\n\n## \ud83d\udce6 \u5b89\u88c5\n\n### \u8981\u6c42\n\n- Python 3.8+\n- PostgreSQL 12+\n- AsyncPG \u9a71\u52a8\n\n### \u4f7f\u7528 pip \u5b89\u88c5\n\n\\`\\`\\`bash\n# \u57fa\u7840\u5b89\u88c5\npip install prisma-web3-py\n\n# \u4ece\u6e90\u7801\u5b89\u88c5\uff08\u5f00\u53d1\u7248\uff09\ngit clone https://github.com/your-org/prisma-web3.git\ncd prisma-web3/python\npip install -e .\n\\`\\`\\`\n\n### \u6570\u636e\u5e93\u8bbe\u7f6e\n\n1. **\u521b\u5efa\u6570\u636e\u5e93**\uff1a\n\\`\\`\\`bash\npsql -U postgres\nCREATE DATABASE your_database;\n\\`\\`\\`\n\n2. **\u8fd0\u884c\u8fc1\u79fb**\uff08\u4f7f\u7528 Prisma\uff09\uff1a\n\\`\\`\\`bash\ncd ../ # \u56de\u5230\u9879\u76ee\u6839\u76ee\u5f55\nnpx prisma migrate dev\n\\`\\`\\`\n\n3. **\u914d\u7f6e\u73af\u5883\u53d8\u91cf**\uff1a\n\\`\\`\\`bash\n# .env\nDATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/your_database\n\\`\\`\\`\n\n---\n\n## \ud83d\ude80 \u5feb\u901f\u5f00\u59cb\n\n### 1. \u521d\u59cb\u5316\u6570\u636e\u5e93\u8fde\u63a5\n\n\\`\\`\\`python\nimport asyncio\nfrom prisma_web3_py import init_db, close_db, get_db\n\nasync def main():\n # \u521d\u59cb\u5316\u6570\u636e\u5e93\u8fde\u63a5\u6c60\n await init_db()\n\n try:\n # \u4f60\u7684\u4e1a\u52a1\u903b\u8f91\n async with get_db() as session:\n # \u4f7f\u7528 session \u8fdb\u884c\u6570\u636e\u5e93\u64cd\u4f5c\n pass\n finally:\n # \u5173\u95ed\u8fde\u63a5\u6c60\n await close_db()\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n\\`\\`\\`\n\n### 2. \u4f7f\u7528 Repository \u67e5\u8be2\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, TokenRepository\n\nasync def query_tokens():\n repo = TokenRepository()\n\n async with get_db() as session:\n # \u83b7\u53d6 Token\uff08\u652f\u6301\u94fe\u540d\u7f29\u5199\uff01\uff09\n token = await repo.get_by_address(\n session,\n chain='sol', # \u81ea\u52a8\u8f6c\u6362\u4e3a 'solana'\n token_address='oobQ3oX6ubRYMNMahG7VSCe8Z73uaQbAWFn6f22XTgo'\n )\n\n print(f\"Token: {token.symbol} - {token.name}\")\n print(f\"Chain: {token.chain}\") # \u8f93\u51fa: solana\n\n # \u641c\u7d22 Tokens\n tokens = await repo.search_tokens(session, \"BTC\", limit=10)\n for t in tokens:\n print(f\"- {t.symbol}: {t.name}\")\n\\`\\`\\`\n\n### 3. \u63d2\u5165\u6570\u636e\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, TokenRepository\n\nasync def insert_token():\n repo = TokenRepository()\n\n async with get_db() as session:\n token_data = {\n \"chain\": \"eth\", # \u4f7f\u7528\u7f29\u5199\n \"token_address\": \"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984\",\n \"symbol\": \"UNI\",\n \"name\": \"Uniswap\",\n \"coingecko_id\": \"uniswap\",\n \"decimals\": 18,\n \"platforms\": {\n \"ethereum\": \"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984\",\n \"polygon-pos\": \"0xb33eaad8d922b1083446dc23f610c2567fb5180f\"\n }\n }\n\n token_id = await repo.upsert_token(session, token_data)\n await session.commit()\n\n print(f\"Token saved with ID: {token_id}\")\n\\`\\`\\`\n\n### 4. \u521b\u5efa\u6570\u636e\uff08\u91cd\u8981\uff01\uff09\n\n**\u26a0\ufe0f \u6ce8\u610f**: \u4f7f\u7528 Repository \u7684 `create()` \u65b9\u6cd5\uff0c\u800c\u4e0d\u662f\u76f4\u63a5\u521b\u5efa Model \u5bf9\u8c61\uff01\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, PreSignalRepository\n\nasync def create_pre_signal():\n repo = PreSignalRepository()\n\n async with get_db() as session:\n # \u2705 \u6b63\u786e\uff1a\u4f7f\u7528 repository.create()\uff0c\u81ea\u52a8\u5904\u7406 chain \u6807\u51c6\u5316\n pre_signal = await repo.create(\n session,\n source=\"jin_vip\",\n chain=\"sol\", # \u81ea\u52a8\u8f6c\u6362\u4e3a 'solana'\n token_address=\"oobQ3oX6ubRYMNMahG7VSCe8Z73uaQbAWFn6f22XTgo\",\n signal_type=\"jin_vip\",\n channel_calls=5,\n multi_signals=3,\n kol_discussions=2,\n token_narrative=\"Some description\"\n )\n\n if pre_signal:\n await session.commit()\n print(f\"\u2705 Created PreSignal ID: {pre_signal.id}\")\n else:\n print(\"\u274c Failed to create PreSignal\")\n\n# \u274c \u9519\u8bef\u793a\u4f8b\uff1a\u4e0d\u8981\u76f4\u63a5\u521b\u5efa Model \u5bf9\u8c61\uff01\nasync def wrong_way():\n from prisma_web3_py import PreSignal\n\n async with get_db() as session:\n # \u274c \u8fd9\u6837\u4f1a\u5bfc\u81f4\u5916\u952e\u9519\u8bef\uff0c\u56e0\u4e3a chain \u6ca1\u6709\u6807\u51c6\u5316\uff01\n pre_signal = PreSignal(\n source=\"jin_vip\",\n chain=\"sol\", # \u4e0d\u4f1a\u81ea\u52a8\u8f6c\u6362\uff0c\u5bfc\u81f4\u5916\u952e\u7ea6\u675f\u5931\u8d25\n token_address=\"...\",\n signal_type=\"jin_vip\"\n )\n session.add(pre_signal) # \u274c \u4f1a\u62a5\u9519\uff01\n\\`\\`\\`\n\n**\u4e3a\u4ec0\u4e48\u5fc5\u987b\u4f7f\u7528 Repository\uff1f**\n\n1. **\u81ea\u52a8\u94fe\u540d\u6807\u51c6\u5316**: Repository \u4f1a\u81ea\u52a8\u5c06 `'sol'` \u8f6c\u6362\u4e3a `'solana'`\n2. **\u5916\u952e\u7ea6\u675f**: Token \u8868\u5b58\u50a8\u7684\u662f\u6807\u51c6\u540d\uff0c\u76f4\u63a5\u4f7f\u7528\u7f29\u5199\u4f1a\u8fdd\u53cd\u5916\u952e\u7ea6\u675f\n3. **\u6570\u636e\u9a8c\u8bc1**: Repository \u63d0\u4f9b\u989d\u5916\u7684\u6570\u636e\u9a8c\u8bc1\u548c\u9519\u8bef\u5904\u7406\n4. **\u4e00\u81f4\u6027**: \u786e\u4fdd\u6240\u6709\u6570\u636e\u4ee5\u7edf\u4e00\u683c\u5f0f\u5b58\u50a8\n\n### 5. \u4f7f\u7528 CryptoNews\uff08\u52a0\u5bc6\u65b0\u95fb\uff09\n\n\\`\\`\\`python\nfrom datetime import datetime\nfrom prisma_web3_py import get_db, CryptoNewsRepository\n\nasync def crypto_news_example():\n repo = CryptoNewsRepository()\n\n async with get_db() as session:\n # 1. \u5bfc\u5165\u65b0\u95fb\u6570\u636e\uff08\u4ece API\uff09\n api_data = {\n \"title\": \"OKX \u5c06\u4e0a\u7ebf SEI (Sei)\uff0c2Z (DoubleZero)\u73b0\u8d27\u4ea4\u6613\",\n \"category\": 1,\n \"source\": \"TechFlow\",\n \"content\": \"11 \u6708 14 \u65e5\uff0c\u636e\u5b98\u65b9\u516c\u544a...\",\n \"matchedCurrencies\": [{\"name\": \"SEI\"}, {\"name\": \"2Z\"}],\n \"entityList\": [\"OKX\", \"SEI\", \"2Z\"],\n \"createTime\": \"1763089364248\" # \u6beb\u79d2\u65f6\u95f4\u6233\n }\n\n # \u8f6c\u6362\u65f6\u95f4\u6233\u5e76\u521b\u5efa\u65b0\u95fb\n news_time = datetime.fromtimestamp(int(api_data[\"createTime\"]) / 1000)\n news = await repo.create_news(\n session,\n title=api_data[\"title\"],\n category=api_data[\"category\"],\n source=api_data[\"source\"],\n content=api_data[\"content\"],\n matched_currencies=api_data.get(\"matchedCurrencies\", []),\n entity_list=api_data.get(\"entityList\", []),\n news_created_at=news_time\n )\n await session.commit()\n\n # 2. \u6309\u52a0\u5bc6\u8d27\u5e01\u641c\u7d22\u65b0\u95fb\n btc_news = await repo.search_by_currency(session, \"BTC\", hours=24)\n print(f\"\u5173\u4e8e BTC \u7684\u65b0\u95fb: {len(btc_news)} \u6761\")\n\n # 3. \u6309\u5b9e\u4f53\u641c\u7d22\u65b0\u95fb\n okx_news = await repo.search_by_entity(session, \"OKX\", hours=24)\n print(f\"\u63d0\u5230 OKX \u7684\u65b0\u95fb: {len(okx_news)} \u6761\")\n\n # 4. \u83b7\u53d6\u70ed\u95e8\u8d27\u5e01\n trending = await repo.get_trending_currencies(session, hours=24, limit=10)\n print(\"\u70ed\u95e8\u8d27\u5e01:\")\n for item in trending[:5]:\n print(f\" {item['currency']}: {item['mentions']} \u6b21\u63d0\u53ca\")\n\n # 5. \u83b7\u53d6\u70ed\u95e8\u5b9e\u4f53\n trending_entities = await repo.get_trending_entities(session, hours=24)\n print(\"\u70ed\u95e8\u5b9e\u4f53:\")\n for item in trending_entities[:5]:\n print(f\" {item['entity']}: {item['mentions']} \u6b21\u63d0\u53ca\")\n\n # 6. \u641c\u7d22\u6807\u7b7e\n defi_news = await repo.search_by_tag(session, \"defi\", hours=24)\n print(f\"DeFi \u76f8\u5173\u65b0\u95fb: {len(defi_news)} \u6761\")\n\\`\\`\\`\n\n### 6. \u4f7f\u7528 Models \u76f4\u63a5\u67e5\u8be2\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, Token, Signal\nfrom sqlalchemy import select, func\n\nasync def custom_query():\n async with get_db() as session:\n # \u81ea\u5b9a\u4e49\u590d\u6742\u67e5\u8be2\n stmt = (\n select(Token, func.count(Signal.id).label('signal_count'))\n .join(Signal, (Token.chain == Signal.chain) &\n (Token.token_address == Signal.token_address))\n .group_by(Token.id)\n .order_by(func.count(Signal.id).desc())\n .limit(10)\n )\n\n result = await session.execute(stmt)\n for token, count in result:\n print(f\"{token.symbol}: {count} signals\")\n\\`\\`\\`\n\n---\n\n## \ud83d\udca1 \u6838\u5fc3\u6982\u5ff5\n\n### 1. Repository Pattern\uff08\u4ed3\u50a8\u6a21\u5f0f\uff09\n\nRepository \u662f\u6570\u636e\u8bbf\u95ee\u5c42\u7684\u62bd\u8c61\uff0c\u9690\u85cf\u4e86 SQL \u67e5\u8be2\u7ec6\u8282\u3002\n\n\u6240\u6709 Repository \u90fd\u7ee7\u627f\u81ea `BaseRepository`\uff0c\u63d0\u4f9b\u57fa\u7840 CRUD \u65b9\u6cd5\u3002\n\n### 2. \u94fe\u540d\u89c4\u8303\u5316\n\n\u6240\u6709 Repository \u81ea\u52a8\u5904\u7406\u94fe\u540d\u8f6c\u6362\uff1a\n\n\\`\\`\\`python\n# \u8fd9\u4e9b\u90fd\u53ef\u4ee5\u5de5\u4f5c\nawait repo.get_by_address(session, \"sol\", \"address\") # \u7f29\u5199\nawait repo.get_by_address(session, \"eth\", \"address\") # \u7f29\u5199\nawait repo.get_by_address(session, \"solana\", \"address\") # \u6807\u51c6\u540d\n\n# Repository \u4f1a\u81ea\u52a8\u8f6c\u6362\u4e3a CoinGecko \u6807\u51c6\u540d\u5b58\u5165\u6570\u636e\u5e93\n\\`\\`\\`\n\n\u652f\u6301\u7684\u94fe\uff1aEthereum (`eth`), BSC (`bsc`), Solana (`sol`), Polygon (`poly`), Arbitrum (`arb`), Base (`base`) \u7b49 18+ \u6761\u94fe\u3002\n\n### 3. \u8de8\u94fe Token \u8bbe\u8ba1\n\nToken \u8868\u91c7\u7528\u5355\u8868\u8bbe\u8ba1\u5b58\u50a8\u8de8\u94fe\u8d44\u4ea7\uff1a\n\n\\`\\`\\`python\n{\n \"chain\": \"ethereum\", # \u4e3b\u94fe\uff08\u4f18\u5148\u7ea7\u6700\u9ad8\uff09\n \"token_address\": \"0x...\", # \u4e3b\u94fe\u5730\u5740\n \"symbol\": \"UNI\",\n \"platforms\": { # \u8de8\u94fe\u5730\u5740\u6620\u5c04 (JSONB)\n \"ethereum\": \"0x...\",\n \"polygon-pos\": \"0x...\",\n \"arbitrum-one\": \"0x...\"\n }\n}\n\\`\\`\\`\n\n### 4. \u5f02\u6b65 Context Manager\n\n\u4f7f\u7528 `get_db()` \u81ea\u52a8\u7ba1\u7406 Session \u751f\u547d\u5468\u671f\uff1a\n\n\\`\\`\\`python\nasync with get_db() as session:\n # session \u81ea\u52a8\u521b\u5efa\n result = await repo.get_all(session)\n await session.commit()\n # session \u81ea\u52a8\u5173\u95ed\n\\`\\`\\`\n\n---\n\n## \ud83d\udcda \u8be6\u7ec6\u4f7f\u7528\n\n### \u4f7f\u7528 get_db() \u548c Repository \u7684\u5b8c\u6574\u6307\u5357\n\n#### \u57fa\u7840\u6a21\u5f0f\uff1a\u4f7f\u7528 Context Manager\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, TokenRepository\n\nasync def basic_usage():\n # 1. \u521b\u5efa repository \u5b9e\u4f8b\n repo = TokenRepository()\n\n # 2. \u4f7f\u7528 get_db() \u83b7\u53d6 session\n async with get_db() as session:\n # 3. \u6267\u884c\u6570\u636e\u5e93\u64cd\u4f5c\n token = await repo.get_by_address(\n session,\n chain='sol', # \u81ea\u52a8\u6807\u51c6\u5316\n token_address='xxx'\n )\n\n # 4. \u63d0\u4ea4\u4e8b\u52a1\n await session.commit()\n # session \u81ea\u52a8\u5173\u95ed\n\\`\\`\\`\n\n#### \u5b8c\u6574\u7684\u589e\u5220\u6539\u67e5\u793a\u4f8b\n\n\\`\\`\\`python\nfrom prisma_web3_py import (\n get_db,\n TokenRepository,\n SignalRepository,\n PreSignalRepository\n)\n\nasync def crud_examples():\n token_repo = TokenRepository()\n signal_repo = SignalRepository()\n pre_signal_repo = PreSignalRepository()\n\n async with get_db() as session:\n # ========== CREATE ==========\n\n # \u65b9\u5f0f1: \u4f7f\u7528 repository.create()\uff08\u63a8\u8350\uff09\n pre_signal = await pre_signal_repo.create(\n session,\n source=\"source1\",\n chain=\"sol\", # \u2705 \u81ea\u52a8\u8f6c\u6362\u4e3a 'solana'\n token_address=\"xxx\",\n signal_type=\"type1\",\n channel_calls=5\n )\n\n # \u65b9\u5f0f2: \u4f7f\u7528\u4e13\u7528\u65b9\u6cd5\n signal = await signal_repo.upsert_signal(\n session,\n chain=\"eth\", # \u2705 \u81ea\u52a8\u8f6c\u6362\u4e3a 'ethereum'\n token_address=\"0x123\",\n source=\"source1\",\n signal_type=\"kol\"\n )\n\n # ========== READ ==========\n\n # \u5355\u4e2a\u67e5\u8be2\n token = await token_repo.get_by_address(\n session,\n chain='bsc', # \u2705 \u81ea\u52a8\u8f6c\u6362\u4e3a 'binance-smart-chain'\n token_address='0x456'\n )\n\n # \u6279\u91cf\u67e5\u8be2\n recent_tokens = await token_repo.get_recent_tokens(\n session,\n chain='sol',\n limit=100\n )\n\n # \u641c\u7d22\n search_results = await token_repo.search_tokens(\n session,\n search_term='BTC',\n limit=20\n )\n\n # ========== UPDATE ==========\n\n # \u4f7f\u7528 BaseRepository \u7684 update_by_id\n success = await token_repo.update_by_id(\n session,\n id=token.id,\n symbol='NEW_SYMBOL'\n )\n\n # \u6216\u8005\u4f7f\u7528 upsert\n token_id = await token_repo.upsert_token(\n session,\n {\n 'chain': 'eth',\n 'token_address': '0x789',\n 'symbol': 'UNI',\n 'name': 'Uniswap'\n }\n )\n\n # ========== DELETE ==========\n\n success = await token_repo.delete_by_id(session, id=123)\n\n # \u63d0\u4ea4\u6240\u6709\u66f4\u6539\n await session.commit()\n\\`\\`\\`\n\n#### \u5904\u7406\u7b2c\u4e09\u65b9\u6570\u636e\u7684\u6b63\u786e\u65b9\u5f0f\n\n\u5f53\u4f60\u4ece\u5916\u90e8\u6e90\uff08API\u3001\u6d88\u606f\u961f\u5217\u7b49\uff09\u63a5\u6536\u6570\u636e\u65f6\uff1a\n\n\\`\\`\\`python\nasync def handle_external_data(data: dict):\n \"\"\"\n \u5904\u7406\u5916\u90e8\u6570\u636e\u7684\u6b63\u786e\u65b9\u5f0f\n\n Args:\n data: \u6765\u81ea\u7b2c\u4e09\u65b9\u7684\u6570\u636e\uff0c\u4f8b\u5982\uff1a\n {\n \"source\": \"jin_vip\",\n \"chain\": \"sol\", # \u53ef\u80fd\u662f\u7f29\u5199\n \"token_address\": \"xxx\",\n \"signal_type\": \"jin_vip\",\n \"signals\": {\n \"channel_calls\": 5,\n \"multi_signals\": 3,\n \"kol_discussions\": 2\n },\n \"description\": \"Token narrative...\"\n }\n \"\"\"\n pre_signal_repo = PreSignalRepository()\n\n # \u51c6\u5907\u6570\u636e\n pre_signal_data = {\n \"source\": data.get(\"source\"),\n \"chain\": data.get(\"chain\"), # \u4fdd\u6301\u539f\u6837\uff0crepository \u4f1a\u5904\u7406\n \"token_address\": data.get(\"token_address\"),\n \"signal_type\": data.get(\"signal_type\"),\n }\n\n # \u6dfb\u52a0 signals \u6570\u636e\n signals = data.get(\"signals\", {})\n if signals:\n pre_signal_data.update({\n \"channel_calls\": signals.get(\"channel_calls\", 0),\n \"multi_signals\": signals.get(\"multi_signals\", 0),\n \"kol_discussions\": signals.get(\"kol_discussions\", 0),\n })\n\n # \u6dfb\u52a0\u4ee3\u5e01\u53d9\u4e8b\n if data.get(\"description\"):\n pre_signal_data[\"token_narrative\"] = data[\"description\"]\n\n # \u2705 \u6b63\u786e\u65b9\u5f0f\uff1a\u4f7f\u7528 repository.create()\n async with get_db() as session:\n pre_signal = await pre_signal_repo.create(\n session,\n **{k: v for k, v in pre_signal_data.items() if v is not None}\n )\n\n if pre_signal:\n await session.commit()\n print(f\"\u2705 Created PreSignal ID: {pre_signal.id}\")\n return pre_signal\n else:\n print(\"\u274c Failed to create PreSignal\")\n return None\n\n # \u274c \u9519\u8bef\u65b9\u5f0f\uff1a\u76f4\u63a5\u521b\u5efa Model \u5bf9\u8c61\n # from prisma_web3_py import PreSignal\n # async with get_db() as session:\n # pre_signal = PreSignal(**pre_signal_data) # \u274c chain \u4e0d\u4f1a\u6807\u51c6\u5316\uff01\n # session.add(pre_signal)\n # await session.commit() # \u274c \u5916\u952e\u7ea6\u675f\u9519\u8bef\uff01\n\\`\\`\\`\n\n#### \u6279\u91cf\u64cd\u4f5c\n\n\\`\\`\\`python\nasync def bulk_operations():\n repo = TokenRepository()\n\n async with get_db() as session:\n # \u6279\u91cf\u521b\u5efa\n tokens_data = [\n {\"chain\": \"eth\", \"token_address\": \"0x1\", \"symbol\": \"TKN1\"},\n {\"chain\": \"bsc\", \"token_address\": \"0x2\", \"symbol\": \"TKN2\"},\n {\"chain\": \"sol\", \"token_address\": \"xxx\", \"symbol\": \"TKN3\"},\n ]\n\n for token_data in tokens_data:\n # \u4f7f\u7528 repository.create() \u786e\u4fdd chain \u6807\u51c6\u5316\n token = await repo.create(session, **token_data)\n if not token:\n print(f\"Failed to create {token_data}\")\n\n # \u4e00\u6b21\u6027\u63d0\u4ea4\u6240\u6709\u66f4\u6539\n await session.commit()\n\\`\\`\\`\n\n#### \u9519\u8bef\u5904\u7406\n\n\\`\\`\\`python\nfrom sqlalchemy.exc import SQLAlchemyError, IntegrityError\n\nasync def error_handling():\n repo = PreSignalRepository()\n\n async with get_db() as session:\n try:\n pre_signal = await repo.create(\n session,\n source=\"source1\",\n chain=\"sol\",\n token_address=\"xxx\",\n signal_type=\"type1\"\n )\n\n await session.commit()\n return pre_signal\n\n except IntegrityError as e:\n # \u5916\u952e\u7ea6\u675f\u3001\u552f\u4e00\u7ea6\u675f\u8fdd\u53cd\n await session.rollback()\n print(f\"Integrity error: {e}\")\n raise\n\n except SQLAlchemyError as e:\n # \u5176\u4ed6\u6570\u636e\u5e93\u9519\u8bef\n await session.rollback()\n print(f\"Database error: {e}\")\n raise\n\n except Exception as e:\n # \u5176\u4ed6\u9519\u8bef\n await session.rollback()\n print(f\"Unexpected error: {e}\")\n raise\n\\`\\`\\`\n\n### \u4e3b\u8981\u64cd\u4f5c\u6982\u89c8\n\n- \u2705 **Token \u67e5\u8be2\u3001\u521b\u5efa\u3001\u66f4\u65b0** - \u4f7f\u7528 `TokenRepository`\n- \u2705 **Signal \u7ba1\u7406** - \u4f7f\u7528 `SignalRepository`\n- \u2705 **PreSignal \u5904\u7406** - \u4f7f\u7528 `PreSignalRepository`\n- \u2705 **\u81ea\u5b9a\u4e49\u67e5\u8be2** - \u76f4\u63a5\u4f7f\u7528 Models + SQLAlchemy\n- \u2705 **\u6279\u91cf\u5bfc\u5165** - \u4f7f\u7528 `TokenImporter`\n\n\u8be6\u7ec6 API \u6587\u6863\u8bf7\u53c2\u8003 [API \u53c2\u8003](#-api-\u53c2\u8003) \u90e8\u5206\u3002\n\n---\n\n## \ud83d\udd0c \u6269\u5c55\u5f00\u53d1\n\nPrisma Web3 Python \u5b8c\u5168\u53ef\u6269\u5c55\u3002\u8be6\u7ec6\u6307\u5357\u8bf7\u53c2\u8003 [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md)\u3002\n\n### \u5feb\u901f\u793a\u4f8b\n\n#### 1. \u7ee7\u627f BaseRepository\n\n\\`\\`\\`python\nfrom prisma_web3_py import BaseRepository, Token\n\nclass MyTokenRepository(BaseRepository[Token]):\n async def get_high_value_tokens(self, session, min_supply: float):\n # \u81ea\u5b9a\u4e49\u67e5\u8be2\n pass\n\\`\\`\\`\n\n#### 2. \u4f7f\u7528 Models \u76f4\u63a5\u67e5\u8be2\n\n\\`\\`\\`python\nfrom prisma_web3_py import get_db, Token\nfrom sqlalchemy import select\n\nasync with get_db() as session:\n stmt = select(Token).where(Token.symbol == 'BTC')\n result = await session.execute(stmt)\n\\`\\`\\`\n\n#### 3. \u6269\u5c55\u73b0\u6709 Repository\n\n\\`\\`\\`python\nfrom prisma_web3_py import TokenRepository\n\nclass ExtendedTokenRepository(TokenRepository):\n async def new_feature(self, session):\n # \u6dfb\u52a0\u65b0\u65b9\u6cd5\n pass\n\\`\\`\\`\n\n---\n\n## \ud83d\udcd6 API \u53c2\u8003\n\n### \u6838\u5fc3\u7ec4\u4ef6\n\n\\`\\`\\`python\nfrom prisma_web3_py import (\n # Core\n Base, get_db, init_db, close_db, AsyncSessionLocal,\n\n # Models\n Token, Signal, PreSignal, SignalStatus, CryptoNews,\n\n # Repositories\n BaseRepository, TokenRepository, SignalRepository, PreSignalRepository,\n CryptoNewsRepository,\n\n # Utils\n TokenImporter, ChainConfig\n)\n\\`\\`\\`\n\n### TokenRepository \u4e3b\u8981\u65b9\u6cd5\n\n- `get_by_address(session, chain, token_address)` - \u6309\u94fe\u548c\u5730\u5740\u67e5\u8be2\n- `search_tokens(session, search_term, chain, limit)` - \u641c\u7d22\n- `search_by_symbol(session, symbol, exact)` - \u6309\u7b26\u53f7\u641c\u7d22\n- `search_by_name(session, name, exact)` - \u6309\u540d\u79f0\u641c\u7d22\n- `search_by_alias(session, alias)` - \u6309\u522b\u540d\u641c\u7d22\uff08\u4f7f\u7528 JSONB @> \u64cd\u4f5c\u7b26\uff09\n- `fuzzy_search(session, text, threshold, limit)` - \u6a21\u7cca\u641c\u7d22\uff08\u652f\u6301 pg_trgm\uff09\n- `upsert_token(session, token_data)` - \u63d2\u5165\u6216\u66f4\u65b0\n- `get_recent_tokens(session, chain, limit)` - \u6700\u8fd1\u521b\u5efa\n- `get_recently_updated_tokens(session, hours, chain, limit)` - \u6700\u8fd1\u66f4\u65b0\n\n### CryptoNewsRepository \u4e3b\u8981\u65b9\u6cd5\n\n**\u521b\u5efa\u548c\u57fa\u7840\u67e5\u8be2**:\n- `create_news(session, title, category, source, content, ...)` - \u521b\u5efa\u65b0\u95fb\n- `get_recent_news(session, hours, source, sector, limit)` - \u83b7\u53d6\u6700\u8fd1\u65b0\u95fb\n- `get_news_by_source(session, source, hours, limit)` - \u6309\u6765\u6e90\u67e5\u8be2\n- `get_news_by_sector(session, sector, hours, limit)` - \u6309\u884c\u4e1a\u67e5\u8be2\n\n**JSONB \u9ad8\u7ea7\u67e5\u8be2**\uff08\u4f7f\u7528 PostgreSQL @> \u64cd\u4f5c\u7b26\uff09:\n- `search_by_currency(session, currency_name, hours, limit)` - \u6309\u52a0\u5bc6\u8d27\u5e01\u641c\u7d22\n- `search_by_entity(session, entity_name, hours, limit)` - \u6309\u5b9e\u4f53\u641c\u7d22\n- `search_by_tag(session, tag, hours, limit)` - \u6309\u6807\u7b7e\u641c\u7d22\n\n**\u8d8b\u52bf\u5206\u6790**\uff08\u4f7f\u7528 jsonb_array_elements\uff09:\n- `get_trending_currencies(session, hours, limit)` - \u83b7\u53d6\u70ed\u95e8\u8d27\u5e01\n- `get_trending_entities(session, hours, limit)` - \u83b7\u53d6\u70ed\u95e8\u5b9e\u4f53\n\n**\u5176\u4ed6**:\n- `search_news(session, search_term, search_in_content, limit)` - \u5173\u952e\u8bcd\u641c\u7d22\n- `get_news_statistics(session, hours)` - \u7edf\u8ba1\u4fe1\u606f\n\n\u5b8c\u6574 API \u8bf7\u67e5\u770b\u6e90\u7801\u6ce8\u91ca\u3002\n\n---\n\n## \ud83c\udfaf \u6700\u4f73\u5b9e\u8df5\n\n### 1. \u59cb\u7ec8\u4f7f\u7528 Repository \u521b\u5efa\u6570\u636e\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\n# \u4f7f\u7528 repository.create()\nrepo = PreSignalRepository()\nasync with get_db() as session:\n result = await repo.create(session, chain='sol', ...)\n await session.commit()\n\\`\\`\\`\n\n\u274c **\u907f\u514d**\uff1a\n\\`\\`\\`python\n# \u76f4\u63a5\u521b\u5efa Model \u5bf9\u8c61\nfrom prisma_web3_py import PreSignal\npre_signal = PreSignal(chain='sol', ...) # \u4e0d\u4f1a\u6807\u51c6\u5316 chain\nsession.add(pre_signal)\n\\`\\`\\`\n\n### 2. \u4f7f\u7528 Context Manager\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\nasync with get_db() as session:\n result = await repo.get_all(session)\n await session.commit()\n # session \u81ea\u52a8\u5173\u95ed\uff0c\u8fde\u63a5\u5f52\u8fd8\u5230\u8fde\u63a5\u6c60\n\\`\\`\\`\n\n\u274c **\u907f\u514d**\uff1a\n\\`\\`\\`python\nsession = AsyncSessionLocal() # \u624b\u52a8\u7ba1\u7406\nresult = await repo.get_all(session)\nawait session.close() # \u5bb9\u6613\u5fd8\u8bb0\n\\`\\`\\`\n\n### 3. \u6b63\u786e\u7684\u9519\u8bef\u5904\u7406\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\nfrom sqlalchemy.exc import SQLAlchemyError, IntegrityError\n\nasync with get_db() as session:\n try:\n result = await repo.create(session, **data)\n await session.commit()\n return result\n except IntegrityError as e:\n # \u5916\u952e\u3001\u552f\u4e00\u7ea6\u675f\u9519\u8bef\n await session.rollback()\n logger.error(f\"Integrity error: {e}\")\n raise\n except SQLAlchemyError as e:\n # \u5176\u4ed6\u6570\u636e\u5e93\u9519\u8bef\n await session.rollback()\n logger.error(f\"Database error: {e}\")\n raise\n\\`\\`\\`\n\n### 4. \u4f7f\u7528\u94fe\u540d\u7f29\u5199\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\n# \u4f7f\u7528\u7f29\u5199\uff0c\u66f4\u7b80\u6d01\nawait repo.get_by_address(session, 'sol', 'address')\nawait repo.get_by_address(session, 'eth', 'address')\nawait repo.get_by_address(session, 'bsc', 'address')\n\\`\\`\\`\n\n\u2705 **\u4e5f\u53ef\u4ee5**\uff1a\n\\`\\`\\`python\n# \u4f7f\u7528\u6807\u51c6\u540d\uff0c\u66f4\u660e\u786e\nawait repo.get_by_address(session, 'solana', 'address')\nawait repo.get_by_address(session, 'ethereum', 'address')\n\\`\\`\\`\n\n### 5. \u5904\u7406\u5916\u90e8\u6570\u636e\n\n\u5f53\u63a5\u6536\u5916\u90e8\u6570\u636e\uff08API\u3001\u6d88\u606f\u961f\u5217\uff09\u65f6\uff1a\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\nasync def handle_external_data(data: dict):\n repo = PreSignalRepository()\n async with get_db() as session:\n # repository \u4f1a\u81ea\u52a8\u6807\u51c6\u5316 chain\n result = await repo.create(session, **data)\n await session.commit()\n return result\n\\`\\`\\`\n\n\u274c **\u907f\u514d**\uff1a\n\\`\\`\\`python\nfrom prisma_web3_py import PreSignal\nasync with get_db() as session:\n # \u5916\u90e8 data['chain'] \u53ef\u80fd\u662f 'sol'\uff0c\u4e0d\u4f1a\u88ab\u6807\u51c6\u5316\n obj = PreSignal(**data)\n session.add(obj) # \u274c \u53ef\u80fd\u5bfc\u81f4\u5916\u952e\u9519\u8bef\n\\`\\`\\`\n\n### 6. \u6279\u91cf\u64cd\u4f5c\u7684\u4e8b\u52a1\u5904\u7406\n\n\u2705 **\u63a8\u8350**\uff1a\n\\`\\`\\`python\nasync with get_db() as session:\n for item in items:\n await repo.create(session, **item)\n # \u4e00\u6b21\u6027\u63d0\u4ea4\u6240\u6709\u64cd\u4f5c\n await session.commit()\n\\`\\`\\`\n\n\u274c **\u907f\u514d**\uff1a\n\\`\\`\\`python\n# \u6bcf\u4e2a\u64cd\u4f5c\u90fd\u5355\u72ec\u63d0\u4ea4\nfor item in items:\n async with get_db() as session:\n await repo.create(session, **item)\n await session.commit() # \u6027\u80fd\u4f4e\u4e0b\n\\`\\`\\`\n\n---\n\n## \u2753 \u5e38\u89c1\u95ee\u9898\n\n### Q1: \u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5916\u952e\u7ea6\u675f\u9519\u8bef\uff1f\n\n**A**: \u6700\u5e38\u89c1\u7684\u539f\u56e0\u662f\u76f4\u63a5\u521b\u5efa Model \u5bf9\u8c61\u800c\u4e0d\u662f\u4f7f\u7528 Repository\uff1a\n\n\\`\\`\\`python\n# \u274c \u9519\u8bef\uff1a\u76f4\u63a5\u521b\u5efa\u5bf9\u8c61\nfrom prisma_web3_py import PreSignal\npre_signal = PreSignal(chain='sol', ...) # chain \u4e0d\u4f1a\u6807\u51c6\u5316\nsession.add(pre_signal) # \u274c \u5916\u952e\u9519\u8bef\uff01\n\n# \u2705 \u6b63\u786e\uff1a\u4f7f\u7528 repository\nfrom prisma_web3_py import PreSignalRepository\nrepo = PreSignalRepository()\npre_signal = await repo.create(session, chain='sol', ...) # \u2705 \u81ea\u52a8\u6807\u51c6\u5316\n\\`\\`\\`\n\n**\u89e3\u51b3\u65b9\u6848**: \u59cb\u7ec8\u4f7f\u7528 Repository \u7684 `create()` \u65b9\u6cd5\u6216\u4e13\u7528\u65b9\u6cd5\u6765\u521b\u5efa\u6570\u636e\u3002\n\n### Q2: \u5982\u4f55\u5904\u7406\u94fe\u540d\uff1f\n\n**A**: \u6240\u6709 Repository \u90fd\u81ea\u52a8\u89c4\u8303\u5316\u94fe\u540d\u3002\u4f60\u53ef\u4ee5\u4f7f\u7528\u7f29\u5199\uff08`sol`, `eth`, `bsc`\uff09\u6216\u6807\u51c6\u540d\uff08`solana`, `ethereum`, `binance-smart-chain`\uff09\uff0c\u6570\u636e\u5e93\u4f1a\u7edf\u4e00\u5b58\u50a8\u4e3a CoinGecko \u6807\u51c6\u540d\u3002\n\n**\u94fe\u540d\u6620\u5c04\u8868**:\n- `sol` \u2192 `solana`\n- `eth` \u2192 `ethereum`\n- `bsc` \u2192 `binance-smart-chain`\n- `poly` \u2192 `polygon-pos`\n- `arb` \u2192 `arbitrum-one`\n- `base` \u2192 `base`\n- \u7b49 18+ \u6761\u94fe...\n\n### Q3: \u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 SQLAlchemy Model \u5417\uff1f\n\n**A**: **\u67e5\u8be2\u53ef\u4ee5\uff0c\u521b\u5efa\u4e0d\u5efa\u8bae**\u3002\n\n\\`\\`\\`python\n# \u2705 \u67e5\u8be2\uff1a\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 Model\nfrom prisma_web3_py import Token\nfrom sqlalchemy import select\n\nstmt = select(Token).where(Token.symbol == 'BTC')\nresult = await session.execute(stmt)\n\n# \u274c \u521b\u5efa\uff1a\u4e0d\u8981\u76f4\u63a5\u521b\u5efa Model\ntoken = Token(chain='sol', ...) # \u4e0d\u4f1a\u6807\u51c6\u5316\nsession.add(token) # \u53ef\u80fd\u5bfc\u81f4\u9519\u8bef\n\n# \u2705 \u521b\u5efa\uff1a\u4f7f\u7528 Repository\nrepo = TokenRepository()\ntoken = await repo.create(session, chain='sol', ...) # \u81ea\u52a8\u6807\u51c6\u5316\n\\`\\`\\`\n\n### Q4: \u5982\u4f55\u6267\u884c\u81ea\u5b9a\u4e49\u67e5\u8be2\uff1f\n\n**A**: \u4e09\u79cd\u65b9\u5f0f\uff1a\n1. \u76f4\u63a5\u4f7f\u7528 Models + SQLAlchemy\uff08\u4ec5\u67e5\u8be2\uff09\n2. \u7ee7\u627f BaseRepository\uff08\u6dfb\u52a0\u65b0\u65b9\u6cd5\uff09\n3. \u6269\u5c55\u73b0\u6709 Repository\uff08\u6269\u5c55\u529f\u80fd\uff09\n\n\u8be6\u89c1 [\u6269\u5c55\u5f00\u53d1](#-\u6269\u5c55\u5f00\u53d1) \u6216 [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md)\u3002\n\n### Q5: \u652f\u6301\u54ea\u4e9b\u6570\u636e\u5e93\uff1f\n\n**A**: \u76ee\u524d\u53ea\u652f\u6301 **PostgreSQL**\uff08\u4f7f\u7528 AsyncPG \u9a71\u52a8\uff09\u3002\n\n### Q6: \u5904\u7406\u5916\u90e8\u6570\u636e\u65f6\u5e94\u8be5\u6ce8\u610f\u4ec0\u4e48\uff1f\n\n**A**: \u5916\u90e8\u6570\u636e\uff08API\u3001\u6d88\u606f\u961f\u5217\u7b49\uff09\u53ef\u80fd\u5305\u542b\u94fe\u540d\u7f29\u5199\uff0c\u5fc5\u987b\u901a\u8fc7 Repository \u5904\u7406\uff1a\n\n\\`\\`\\`python\n# \u5916\u90e8\u6570\u636e\ndata = {\"chain\": \"sol\", \"token_address\": \"xxx\", ...}\n\n# \u2705 \u6b63\u786e\uff1a\u4f7f\u7528 repository.create()\nrepo = PreSignalRepository()\nasync with get_db() as session:\n result = await repo.create(session, **data)\n await session.commit()\n\\`\\`\\`\n\n\u8be6\u89c1 [\u8be6\u7ec6\u4f7f\u7528](#-\u8be6\u7ec6\u4f7f\u7528) \u4e2d\u7684\"\u5904\u7406\u7b2c\u4e09\u65b9\u6570\u636e\u7684\u6b63\u786e\u65b9\u5f0f\"\u3002\n\n---\n\n## \ud83d\udcda \u6587\u6863\n\n- **\u6269\u5c55\u6307\u5357**: [EXTENSION_GUIDE.md](EXTENSION_GUIDE.md) - \u5982\u4f55\u6269\u5c55\u6a21\u5757\n- **\u67b6\u6784\u6587\u6863**: [ARCHITECTURE.md](ARCHITECTURE.md) - \u7cfb\u7edf\u67b6\u6784\u8bf4\u660e\n- **\u5bfc\u5165\u6307\u5357**: [IMPORT_GUIDE.md](IMPORT_GUIDE.md) - Token \u6570\u636e\u5bfc\u5165\n\n---\n\n## \ud83d\udee0\ufe0f \u5f00\u53d1\u5de5\u5177\n\n### \u6d4b\u8bd5\n\n\\`\\`\\`bash\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\npython scripts/run_all_tests.py\n\n# \u8fd0\u884c\u7279\u5b9a\u6d4b\u8bd5\npython scripts/test_token.py\npython scripts/test_signal.py\npython scripts/test_pre_signal.py\n\\`\\`\\`\n\n### \u6570\u636e\u5bfc\u5165\n\n\\`\\`\\`bash\n# \u5bfc\u5165 token \u6570\u636e\npython scripts/import_token_recognition_data.py\n\\`\\`\\`\n\n### \u9a8c\u8bc1\n\n\\`\\`\\`bash\n# \u9a8c\u8bc1\u6570\u636e\u4e00\u81f4\u6027\npython scripts/verify_consistency.py\n\n# \u6d4b\u8bd5\u6570\u636e\u5e93\u8fde\u63a5\npython scripts/test_connection.py\n\\`\\`\\`\n\n---\n\n## \ud83d\udcdd \u66f4\u65b0\u65e5\u5fd7\n\n### v0.1.8 (\u6700\u65b0)\n- \u2728 \u5b8c\u5168\u66b4\u9732 Models\u3001Repositories\u3001Session \u7b49\u7ec4\u4ef6\n- \u2728 \u65b0\u589e\u6269\u5c55\u6307\u5357\uff08EXTENSION_GUIDE.md\uff09\n- \ud83d\udc1b \u4fee\u590d\u5916\u952e\u7ea6\u675f\u95ee\u9898\uff08\u94fe\u540d\u89c4\u8303\u5316\uff09\n- \ud83d\udcda \u65b0\u589e\u8be6\u7ec6 README \u6587\u6863\n\n### v0.1.6\n- \u2728 \u65b0\u589e\u94fe\u540d\u81ea\u52a8\u89c4\u8303\u5316\u529f\u80fd\n- \u2728 TokenRepository \u65b0\u589e\u591a\u79cd\u641c\u7d22\u65b9\u6cd5\n- \u267b\ufe0f \u79fb\u9664 TokenRecognition \u6a21\u5757\n\n---\n\n## \ud83d\udcc4 \u8bb8\u53ef\u8bc1\n\n\u672c\u9879\u76ee\u91c7\u7528 [MIT](LICENSE) \u8bb8\u53ef\u8bc1\u3002\n\n---\n\n## \ud83e\udd1d \u8d21\u732e\u6307\u5357\n\n\u6b22\u8fce\u8d21\u732e\uff01\u8bf7\u9075\u5faa\u4ee5\u4e0b\u6b65\u9aa4\uff1a\n\n1. Fork \u672c\u4ed3\u5e93\n2. \u521b\u5efa\u7279\u6027\u5206\u652f (`git checkout -b feature/AmazingFeature`)\n3. \u63d0\u4ea4\u66f4\u6539 (`git commit -m 'Add some AmazingFeature'`)\n4. \u63a8\u9001\u5230\u5206\u652f (`git push origin feature/AmazingFeature`)\n5. \u5f00\u542f Pull Request\n\n---\n\n<div align=\"center\">\n\n**\u5982\u679c\u8fd9\u4e2a\u9879\u76ee\u5bf9\u4f60\u6709\u5e2e\u52a9\uff0c\u8bf7\u7ed9\u6211\u4eec\u4e00\u4e2a \u2b50\ufe0f\uff01**\n\nMade with \u2764\ufe0f by the Prisma Web3 Team\n\n</div>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python/SQLAlchemy async implementation of Prisma Web3 database models",
"version": "0.2.3",
"project_urls": {
"Documentation": "https://github.com/your-org/prisma-web3/tree/main/python",
"Homepage": "https://github.com/your-org/prisma-web3",
"Issues": "https://github.com/your-org/prisma-web3/issues",
"Repository": "https://github.com/your-org/prisma-web3"
},
"split_keywords": [
"database",
" orm",
" sqlalchemy",
" async",
" web3",
" prisma"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "03fa8cc6a37102f76ee5852bf88a4d2e49b56b9bd9c46a8da56949a8ed0a2c8c",
"md5": "8cbc2c8f4e01b31673887f2aecf93289",
"sha256": "e28fd3a8e63adb9be117090d067180082001bdfd0c1a3f4887b588ee1f7049ea"
},
"downloads": -1,
"filename": "prisma_web3_py-0.2.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "8cbc2c8f4e01b31673887f2aecf93289",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 50882,
"upload_time": "2025-11-14T03:23:02",
"upload_time_iso_8601": "2025-11-14T03:23:02.616734Z",
"url": "https://files.pythonhosted.org/packages/03/fa/8cc6a37102f76ee5852bf88a4d2e49b56b9bd9c46a8da56949a8ed0a2c8c/prisma_web3_py-0.2.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "7c7ff506b80fa1072d6cc196c4a4800fa73f195fdf33cf6427528c65e9580d42",
"md5": "e5a6776adfaaba279a1b3aaac9d69098",
"sha256": "02d81cd712181aa1f8606b34120c0874f4e428b27eae16676ed839b30ab2e976"
},
"downloads": -1,
"filename": "prisma_web3_py-0.2.3.tar.gz",
"has_sig": false,
"md5_digest": "e5a6776adfaaba279a1b3aaac9d69098",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 457350,
"upload_time": "2025-11-14T03:23:05",
"upload_time_iso_8601": "2025-11-14T03:23:05.307914Z",
"url": "https://files.pythonhosted.org/packages/7c/7f/f506b80fa1072d6cc196c4a4800fa73f195fdf33cf6427528c65e9580d42/prisma_web3_py-0.2.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-14 03:23:05",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "your-org",
"github_project": "prisma-web3",
"github_not_found": true,
"lcname": "prisma-web3-py"
}